001/* 002 * PermissionsEx 003 * Copyright (C) zml and PermissionsEx contributors 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package ca.stellardrift.permissionsex.subject; 018 019import io.leangen.geantyref.GenericTypeReflector; 020import io.leangen.geantyref.TypeToken; 021import org.checkerframework.checker.nullness.qual.Nullable; 022import org.pcollections.HashTreePMap; 023import org.pcollections.PMap; 024 025import java.util.Map; 026import java.util.function.Function; 027import java.util.function.Supplier; 028 029import static java.util.Objects.requireNonNull; 030 031/** 032 * A definition for parameters controlling a {@link SubjectTypeCollection}'s handling. 033 * 034 * @param <I> identifier type 035 * @since 2.0.0 036 */ 037public final class SubjectType<I> { 038 private final String name; 039 private final Class<I> identifierType; 040 private final boolean transientHasPriority; 041 private final Function<I, Boolean> undefinedValueProvider; 042 private final Function<String, I> identifierDeserializer; 043 private final Function<I, String> identifierSerializer; 044 private final Function<I, @Nullable ?> associatedObjectProvider; 045 private final Function<String, I> friendlyDeserializer; 046 047 /** 048 * Create a new builder for subject types. 049 * 050 * @param name the subject type name 051 * @param identifierType explicit type of identifiers 052 * @param <V> the identifier type 053 * @return a builder for a subject type 054 * @since 2.0.0 055 */ 056 public static <V> Builder<V> builder(final String name, final Class<V> identifierType) { 057 return new Builder<>(name, identifierType); 058 } 059 060 /** 061 * Create a new builder for subject types. 062 * 063 * @param name the subject type name 064 * @param identifierType explicit type of identifiers 065 * @param <V> the identifier type 066 * @return a builder for a subject type 067 * @since 2.0.0 068 */ 069 @SuppressWarnings({"unchecked", "rawtypes"}) 070 public static <V> Builder<V> builder(final String name, final TypeToken<V> identifierType) { 071 return new Builder(name, GenericTypeReflector.erase(identifierType.getType())); 072 } 073 074 /** 075 * Create a new builder for subject types using a string identifier. 076 * 077 * @param name the subject type name 078 * @return a builder for a subject type 079 * @since 2.0.0 080 */ 081 public static Builder<String> stringIdentBuilder(final String name) { 082 return builder(name, String.class) 083 .serializedBy(Function.identity()) 084 .deserializedBy(Function.identity()); 085 } 086 087 /** 088 * Create a subject type from a builder. 089 * 090 * @param builder the builder 091 */ 092 SubjectType(final Builder<I> builder) { 093 this.name = builder.name; 094 this.identifierType = builder.identifierType; 095 this.transientHasPriority = builder.transientHasPriority; 096 this.undefinedValueProvider = builder.undefinedValueProvider; 097 this.associatedObjectProvider = builder.associatedObjectProvider; 098 this.friendlyDeserializer = builder.friendlyDeserializer; 099 this.identifierDeserializer = requireNonNull(builder.identifierDeserializer, "Identifier deserializer has not been provided for subject type " + this.name); 100 this.identifierSerializer = requireNonNull(builder.identifierSerializer, "Identifier serializer has not been set for subject type " + this.name); 101 } 102 103 /** 104 * The name of the subject type this defines. 105 * 106 * @return the type name 107 * @since 2.0.0 108 */ 109 public final String name() { 110 return this.name; 111 } 112 113 /** 114 * Return whether or not transient data takes priority over persistent for this subject type. 115 * 116 * @return Whether or not transient data has priority. 117 * @since 2.0.0 118 */ 119 public final boolean transientHasPriority() { 120 return this.transientHasPriority; 121 } 122 123 /** 124 * Check if a name is a valid identifier for a given subject collection 125 * 126 * @param serialized The identifier to check 127 * @return Whether or not the given name is a valid identifier 128 */ 129 public boolean isIdentifierValid(final String serialized) { 130 try { 131 parseIdentifier(serialized); 132 return true; 133 } catch (final InvalidIdentifierException ex) { 134 return false; 135 } 136 } 137 138 /** 139 * Parse an identifier given its serialized string representation. 140 * 141 * @param input the serialized form 142 * @return a parsed identifier 143 * @throws InvalidIdentifierException if an identifier is not of appropriate format for 144 * this subject type. 145 */ 146 public I parseIdentifier(final String input) { 147 return this.identifierDeserializer.apply(requireNonNull(input, "input")); 148 } 149 150 /** 151 * Serialize an identifier to its canonical represenattion. 152 * 153 * @param input the identifier 154 * @return the canonical representation of the identifier 155 */ 156 public String serializeIdentifier(final I input) { 157 return this.identifierSerializer.apply(requireNonNull(input, "input")); 158 } 159 160 /** 161 * Attempt to parse an identifier, while also attempting to resolve from any user-friendly 162 * display name that may be available. 163 * 164 * <p>Unlike {@link #parseIdentifier(String)}, this will not throw a 165 * {@link InvalidIdentifierException} when identifiers are of an invalid format. Instead it may 166 * attempt to perform some sort of lookup to resolve an identifier from the 167 * provided information.</p> 168 * 169 * @param name The friendly name that may be used 170 * @return A standard representation of the subject identifier 171 */ 172 public @Nullable I parseOrCoerceIdentifier(String name) { 173 try { 174 return this.parseIdentifier(name); 175 } catch (final InvalidIdentifierException ex) { 176 return this.friendlyDeserializer.apply(name); 177 } 178 } 179 180 /** 181 * The native object that may be held 182 * 183 * @param identifier type 184 * @return A native object that has its permissions defined by this subject 185 */ 186 public @Nullable Object getAssociatedObject(I identifier) { 187 return this.associatedObjectProvider.apply(identifier); 188 } 189 190 /** 191 * The boolean value an undefined permission should have for this subject type 192 */ 193 public boolean undefinedPermissionValue(final I identifier) { 194 return this.undefinedValueProvider.apply(requireNonNull(identifier)); 195 } 196 197 @Override 198 public int hashCode() { 199 return 7 * this.name.hashCode() 200 + 31 * this.identifierType.hashCode(); 201 } 202 203 @Override 204 public boolean equals(final Object other) { 205 if (!(other instanceof SubjectType)) { 206 return false; 207 } 208 209 final SubjectType<?> that = (SubjectType<?>) other; 210 return this.name.equals(that.name) 211 && this.identifierType.equals(that.identifierType); 212 } 213 214 @Override 215 public String toString() { 216 return "SubjectType<" + this.identifierType.getSimpleName() + ">(name=" + this.name + ")"; 217 } 218 219 /** 220 * A builder for a subject type 221 * @param <I> identifier type 222 */ 223 public static final class Builder<I> { 224 private final String name; 225 private final Class<I> identifierType; 226 private boolean transientHasPriority = true; 227 private Function<I, Boolean> undefinedValueProvider = $ -> false; 228 private Function<I, @Nullable ?> associatedObjectProvider = $ -> null; 229 private Function<String, @Nullable I> friendlyDeserializer = $ -> null; 230 private @Nullable Function<String, I> identifierDeserializer; // required 231 private @Nullable Function<I, String> identifierSerializer; // required 232 233 Builder(final String name, final Class<I> identifierType) { 234 requireNonNull(name, "name"); 235 requireNonNull(identifierType, "identifierType"); 236 this.name = name; 237 this.identifierType = identifierType; 238 } 239 240 Builder(final SubjectType<I> existing) { 241 this.name = existing.name; 242 this.identifierType = existing.identifierType; 243 this.transientHasPriority = existing.transientHasPriority; 244 this.undefinedValueProvider = existing.undefinedValueProvider; 245 this.associatedObjectProvider = existing.associatedObjectProvider; 246 this.friendlyDeserializer = existing.friendlyDeserializer; 247 this.identifierDeserializer = existing.identifierDeserializer; 248 this.identifierSerializer = existing.identifierSerializer; 249 } 250 251 /** 252 * Whether or not this subject resolves data transient-first or persistent first. 253 * 254 * <p>The default value for this property is {@code true}.</p> 255 * 256 * @param priority if transient data should take priority over persistent. 257 * @return this builder 258 * @since 2.0.0 259 */ 260 public Builder<I> transientHasPriority(final boolean priority) { 261 this.transientHasPriority = priority; 262 return this; 263 } 264 265 /** 266 * Set the provider for a fallback permissions value when an undefined 267 * value ({@code 0}) is resolved. 268 * 269 * @param provider the value provider 270 * @return this builder 271 * @since 2.0.0 272 */ 273 public Builder<I> undefinedValues(final Function<I, Boolean> provider) { 274 requireNonNull(provider, "provider"); 275 this.undefinedValueProvider = provider; 276 return this; 277 } 278 279 public Builder<I> serializedBy(final Function<I, String> serializer) { 280 this.identifierSerializer = requireNonNull(serializer, "serializer"); 281 return this; 282 } 283 284 /** 285 * Attempt to deserialize an identifier from the raw input 286 * 287 * <p>On failure, the function may throw a {@link InvalidIdentifierException}</p> 288 * 289 * @param deserializer the deserialization function 290 * @return this builder 291 */ 292 public Builder<I> deserializedBy(final Function<String, I> deserializer) { 293 this.identifierDeserializer = requireNonNull(deserializer, "deserializer"); 294 return this; 295 } 296 297 /** 298 * Provide a function that can resolve a deserialized identifier from a 'friendly' name. 299 * 300 * <p>This function should never throw an {@link InvalidIdentifierException}.</p> 301 * 302 * @param coercer the coercion function 303 * @return this builder 304 */ 305 public Builder<I> friendlyNameResolvedBy(final Function<String, @Nullable I> coercer) { 306 this.friendlyDeserializer = requireNonNull(coercer, "coercer"); 307 return this; 308 } 309 310 /** 311 * Set a provider for associated objects. 312 * 313 * @param provider the associated object provider. may return null if no associated object is available. 314 * @return this builder 315 * @since 2.0.0 316 */ 317 public Builder<I> associatedObjects(final Function<I, @Nullable ?> provider) { 318 this.associatedObjectProvider = provider; 319 return this; 320 } 321 322 /** 323 * Create a subject type with a fixed set of entries. 324 * 325 * @param entries a map of identifier to associated object provider 326 * @return this builder 327 * @since 2.0.0 328 */ 329 public Builder<I> fixedEntries(final Map<I, ? extends Supplier<@Nullable ?>> entries) { 330 requireNonNull(entries, "entries"); 331 332 // Use the map for discovering associated objects 333 this.associatedObjectProvider = ident -> { 334 final Supplier<?> value = entries.get(ident); 335 return value == null ? null : value.get(); 336 }; 337 338 // And restrict the range of our identifier deserializer to available entries. 339 final @Nullable Function<String, I> oldDeserializer = this.identifierDeserializer; 340 if (oldDeserializer == null) { 341 throw new IllegalStateException("An identifier deserializer must have already been set " 342 + "to be able to restrict the valid identifiers."); 343 } 344 this.identifierDeserializer = serialized -> { 345 final I candidate = oldDeserializer.apply(serialized); 346 if (!entries.containsKey(candidate)) { 347 throw new InvalidIdentifierException(serialized); 348 } 349 return candidate; 350 }; 351 return this; 352 } 353 354 /** 355 * Create a subject type with a fixed set of entries. 356 * 357 * @param entries a map of identifier to associated object provider 358 * @return this builder 359 * @since 2.0.0 360 */ 361 @SafeVarargs 362 public final Builder<I> fixedEntries(final Map.Entry<I, ? extends Supplier<@Nullable ?>>... entries) { 363 PMap<I, Supplier<@Nullable ?>> result = HashTreePMap.empty(); 364 for (final Map.Entry<I, ? extends Supplier<@Nullable ?>> entry : entries) { 365 result = result.plus(entry.getKey(), entry.getValue()); 366 } 367 return this.fixedEntries(result); 368 } 369 370 /** 371 * Create a subject type from the provided parameters 372 * 373 * @return the parameters 374 */ 375 public SubjectType<I> build() { 376 return new SubjectType<>(this); 377 } 378 379 } 380}