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