package pluginbase.config.serializers;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import pluginbase.config.SerializableConfig;
import pluginbase.config.annotation.FauxEnum;
import pluginbase.config.annotation.SerializeWith;
import pluginbase.config.serializers.NumberSerializer.AtomicIntegerSerializer;
import pluginbase.config.serializers.NumberSerializer.AtomicLongSerializer;
import pluginbase.config.serializers.NumberSerializer.BigNumberSerializer;
import pluginbase.config.util.PrimitivesUtil;
import pluginbase.logging.Logging;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Predicate;
import java.util.function.Supplier;
/**
* A defined set of serializers used internally to lookup the appropriate serializer for various object types.
* <p/>
* Custom serializer set's can be created and passed to the various {@link SerializableConfig} methods
* in order to use custom serializers in cases where {@link pluginbase.config.annotation.SerializeWith} is not feasible.
* <p/>
* <strong>Note:</strong> Serializer sets are immutable once created.
* <p/>
* Serializers have a general order of priority in which they should be used. See {@link #getClassSerializer(Class)}
* for details.
*
* @see SerializableConfig
*/
public final class SerializerSet {
/**
* Returns the default set of built in serializers used by Serializable-Config.
* <p/>
* The default set includes serializers for the following types:
* <ul>
* <li>All primitive type wrapper classes *</li>
* <li>{@link BigInteger}</li>
* <li>{@link BigDecimal}</li>
* <li>{@link AtomicInteger}</li>
* <li>{@link AtomicLong}</li>
* <li>{@link String}</li>
* <li>{@link Locale}</li>
* <li>All {@link Enum} types</li>
* <li>All array types</li>
* <li>Classes annotated with {@link FauxEnum}</li>
* <li>Classes implementing {@link Collection}</li>
* <li>Classes implementing {@link Map}</li>
* </ul>
* * Primitive types can be converted to their wrapper type via {@link pluginbase.config.util.PrimitivesUtil#switchForWrapper(Class)}.
* <p/>
* All custom serializer sets start out containing a copy of the serializers found in the default set.
*
* @return the default set of built in serializers used by Serializable-Config.
*/
public static SerializerSet defaultSet() {
return DEFAULT_SET;
}
/**
* Creates a Builder object to construct an immutable SerializerSet.
* <p/>
* By default, standard serializers include all those found in the {@link #defaultSet()} but can be replaced as
* necessary.
*
* @return a Builder object to construct an immutable SerializerSet.
*/
@NotNull
public static Builder builder() {
return builder(defaultSet());
}
/**
* Creates a Builder object to construct an immutable SerializerSet.
* <p/>
* This set will begin with all serializers that exist in the given copy set.
*
* @param setToCopy the set of serializers to use as the base for the new serializer set.
* @return a Builder object to construct an immutable SerializerSet.
*/
public static Builder builder(@NotNull SerializerSet setToCopy) {
return new Builder(setToCopy);
}
@NotNull
private final Map<Class, Supplier<Serializer>> serializers;
@NotNull
private final Map<Class, Supplier<Serializer>> overrideSerializers;
@NotNull
private final Supplier<Serializer> fallbackSerializer;
@NotNull
private final Map<Class<? extends Serializer>, Supplier<Serializer>> serializeWithSerializers;
@NotNull
private final Map<Predicate<Class<?>>, Class> classReplacements;
/**
* A builder class used to construct an immutable SerializerSet.
*/
public static class Builder {
@NotNull
private final Map<Class, Supplier<Serializer>> serializers = new HashMap<>();
@NotNull
private final Map<Class, Supplier<Serializer>> overrideSerializers = new HashMap<>();
@NotNull
private Supplier<Serializer> fallbackSerializer;
@NotNull
private final Map<Class<? extends Serializer>, Supplier<Serializer>> serializeWithSerializers;
@NotNull
private final Map<Predicate<Class<?>>, Class> classReplacements;
private Builder(@NotNull SerializerSet setToCopy) {
serializers.putAll(setToCopy.serializers);
overrideSerializers.putAll(setToCopy.overrideSerializers);
fallbackSerializer = setToCopy.fallbackSerializer;
serializeWithSerializers = new HashMap<>(setToCopy.serializeWithSerializers);
classReplacements = new LinkedHashMap<>(setToCopy.classReplacements);
}
/**
* Specifies a standard serializer to be included in the SerializerSet built by the Builder object.
* <p/>
* This serializers will be used after any override serializers specified by {@link #addOverrideSerializer(Class, Supplier)}
* and any serializer specified by {@link pluginbase.config.annotation.SerializeWith}.
*
* @param clazz the class that the given serializer is responsible for serializing/deserializing.
* @param serializer A supplier that provides the instance of the serializer to be used for the given class type.
* @param <T> the class type that the give serializer is for.
* @return this builder object.
*/
@NotNull
public <T> Builder addSerializer(@NotNull Class<T> clazz, @NotNull Supplier<Serializer<T>> serializer) {
serializers.put(clazz, serializer::get);
return this;
}
// Override serializers are very specific, and will not apply to subclasses.
/**
* Specifies an override serializer to be included in the SerializerSet built by the Builder object.
* <p/>
* If a serializer is defined here it will always be used for the given class except in the special case
* illustrated in {@link #getClassSerializer(Class)}.
*
* @param clazz the class that the given serializer is responsible for serializing/deserializing.
* @param serializer A supplier that provides the instance of the serializer to be used for the given class type.
* @param <T> the class type that the give serializer is for.
* @return this builder object.
*/
@NotNull
public <T> Builder addOverrideSerializer(@NotNull Class<T> clazz, @NotNull Supplier<Serializer<T>> serializer) {
overrideSerializers.put(clazz, serializer::get);
return this;
}
/**
* Specifies a new fallback serializer to use instead of the default.
* <p/>
* This should not be used unless you're sure you know what you're doing!
* <p/>
* The fallback serializer will handle all serialization that is not handled by a more specific serializer.
* It should be capable of serializing any kind of object. It should also be thread safe.
*
* @param fallbackSerializer A supplier that provides the new fallback serializer to use in the SerializerSet
* built by this builder object.
* @return this builder object.
*/
public Builder setFallbackSerializer(@NotNull Supplier<Serializer<Object>> fallbackSerializer) {
this.fallbackSerializer = fallbackSerializer::get;
return this;
}
/**
* Registers an instance of a given Serializer class for use with {@link SerializeWith}.
* <p/>
* For a class to be serialized by the serializer specified in {@link SerializeWith} the serializer must have a
* registered instance. If the serializer has a 0-arg constructor, an instance will be created automatically.
* This method allows for the registering of serializer instances for serializers without 0-arg constructors
* and also to replace previous instances should that be needed.
* <p/>
* If the given serializer class has already been registered this method will <strong>replace</strong> the previous
* instance with the given instance.
*
* @param serializerClass the serializer class to register an instance of.
* @param serializer A supplier that provides the instance of the serializer class to use.
* @param <T> the serializer class type.
* @return this builder object.
*/
public <T extends Serializer> Builder registerSerializeWithInstance(@NotNull Class<T> serializerClass, @NotNull Supplier<T> serializer) {
serializeWithSerializers.put(serializerClass, serializer::get);
return this;
}
/**
* Registers a {@link Predicate} that can be used to check if a class about to be serialized should be treated as the
* given replacement class for serialization purposes.
* <p/>
* A very common example for this is serializing all classes that extend a certain class. This can be done
* like so <code>registerClassReplacement(SomeClass.class::isAssignableFrom, SomeClass.class);</code>
* <p/>
* It is recommended that the overriden {@link Predicate#test(Object)} method be as fast as possible sine it
* will potentially be checked very frequently.
* <p/>
* Predicates will be checked in the order they are registered and the first one that matches will be used.
* There are a few default predicates that are registered in the following order:
* <ol>
* <li>@FauxEnum annotation present (replaces with FauxEnum.class)</li>
* <li>Instance of Collection (replaces with Collection.class)</li>
* <li>Instance of Map (replaces with Map.class)</li>
* <li>Instance of Enum (replaces with Enum.class)</li>
* <li>An array (replaces with Array.class)</li>
* </ol>
*
* @param checker the Predicate used to check if a given class should be treated like the replacementClass for
* serialization purposes.
* @param replacementClass the replacement class to use when the checker returns true.
* @return this builder object.
*/
public Builder registerClassReplacement(@NotNull Predicate<Class<?>> checker, @NotNull Class replacementClass) {
classReplacements.put(checker, replacementClass);
return this;
}
/**
* Unregisters all {@link Predicate}s that will cause a replacement with the given replacementClass.
* <p/>
* This may be useful if the order of checks needs to be altered or one of the default predicates needs to be
* replaced.
*
* @param replacementClass The replacement class to unregister all predicates for.
* @return this builder object.
*/
public Builder unregisterClassReplacement(@NotNull Class replacementClass) {
Iterator<Entry<Predicate<Class<?>>, Class>> replacementsIterator = classReplacements.entrySet().iterator();
while (replacementsIterator.hasNext()) {
if (replacementsIterator.next().getValue().equals(replacementClass)) {
replacementsIterator.remove();
}
}
return this;
}
/**
* Constructs the immutable serializer set defined by this builder's methods.
*
* @return the immutable serializer set defined by this builder's methods.
*/
@NotNull
public SerializerSet build() {
return new SerializerSet(serializers, overrideSerializers, fallbackSerializer, serializeWithSerializers, classReplacements);
}
}
private SerializerSet(@NotNull Map<Class, Supplier<Serializer>> serializers, @NotNull Map<Class, Supplier<Serializer>> overrideSerializers,
@NotNull Supplier<Serializer> fallbackSerializer, @NotNull Map<Class<? extends Serializer>, Supplier<Serializer>> serializeWithSerializers,
@NotNull Map<Predicate<Class<?>>, Class> classReplacements) {
this.serializers = Collections.unmodifiableMap(new HashMap<>(serializers));
this.overrideSerializers = Collections.unmodifiableMap(new HashMap<>(overrideSerializers));
this.fallbackSerializer = fallbackSerializer;
this.serializeWithSerializers = new ConcurrentHashMap<>(serializeWithSerializers);
this.classReplacements = Collections.unmodifiableMap(new LinkedHashMap<>(classReplacements));
}
/**
* Retrieves the most appropriate serializer instance capable of serializing/deserializing the given class.
* <p/>
* This is the primary method for obtaining a serializer instance to serialize a given class in nearly all
* scenarios. It will ensure that the most appropriate serializer is obtained.
* <p/>
* Special treatment is given to classes that extend {@link Collection} and {@link Map} and classes annotated by
* {@link FauxEnum}. When looking for a <em>non-override</em> serializer for these classes, if one is not found for
* the specific class, the serializer for Collection, Map, or FauxEnum, respectively, will be used instead. For
* other types, serializers are only returned for the exact type given.
* <p/>
* What serializer is returned is determined by the following priority order:
* <ol>
* <li>An override serializer specified in the given SerializerSet</li>
* <li>The serializer specified by @SerializeWith</li>
* <li>A standard serializer specified in the given SerializerSet</li>
* <li>The serializer set's fallback serializer</li>
* </ol>
* <strong>Note:</strong> An exception to this order is the case of a field (rather than a type) annotated by
* {@link SerializeWith}. The <em>default</em> fallback serializer will prefer the annotated field's
* {@link SerializeWith} serializer to all others. This may or may not hold true if a custom serializer is used on
* the object containing the field or the serializer set has a custom fallback serializer.
*
* @param clazz the class to get a serializer for.
* @return the most appropriate serializer for the given class.
*/
@NotNull
public <T> Serializer<T> getClassSerializer(@NotNull Class<T> clazz) {
clazz = PrimitivesUtil.switchForWrapper(clazz);
Serializer<T> serializer = getOverrideSerializer(clazz);
if (serializer != null) {
return serializer;
}
SerializeWith serializeWith = clazz.getAnnotation(SerializeWith.class);
if (serializeWith != null) {
try {
return getSerializerInstance(serializeWith.value());
} catch (Exception e) {
Logging.warning("Class %s is annotated with SerializeWith and specified the serializer class %s but "
+ "could not obtain an instance of that serializer. Consider registering the serializer "
+ "manually.",
clazz, serializeWith.value());
}
}
serializer = getSerializer(clazz);
if (serializer != null) {
return serializer;
}
for (Entry<Predicate<Class<?>>, Class> entry : classReplacements.entrySet()) {
if (entry.getKey().test(clazz)) {
serializer = getSerializer(entry.getValue());
break;
}
}
if (serializer != null) {
return serializer;
}
return getFallbackSerializer();
}
/**
* Checks if this set contains a serializer for the specific given type. The fallback serializer does not count.
*
* @param serializableClass the class to check for the existence of a serializer for.
* @return true if this serializer set contains a serializer for the exact type given.
*/
public boolean hasSerializerForClass(@NotNull Class serializableClass) {
return serializers.containsKey(serializableClass) || overrideSerializers.containsKey(serializableClass);
}
/**
* Retrieves the global instance for the given serializer class.
* <p/>
* If an instance has not be previously registered, the serializer will be instantiated and registered before being
* returned. If the serializer has not been previously registered and does not have a 0-arg constructor an exception
* will be thrown.
*
* @param serializerClass The serializer class to get the global instance for.
* @param <S> The serializer class type.
* @return The global instance of the given serializer class.
* @throws IllegalArgumentException thrown when a previously unregistered serializer class is given that does not
* have a 0-arg constructor.
*/
@NotNull
@SuppressWarnings("unchecked")
public <S extends Serializer> S getSerializerInstance(@NotNull Class<S> serializerClass) throws IllegalArgumentException {
if (serializeWithSerializers.containsKey(serializerClass)) {
return (S) serializeWithSerializers.get(serializerClass).get();
}
try {
S serializer = createInstance(serializerClass);
registerSerializerInstance(serializerClass, serializer);
return serializer;
} catch (Exception e) {
throw new IllegalArgumentException("Could not instantiate " + serializerClass + ". Does it have a 0-arg constructor?", e);
}
}
/**
* Retrieves the standard serializer for the specific given type. This is the last serializer choice checked before
* the fallback serializer is used.
*
* @see #getClassSerializer(Class)
*
* @param serializableClass the class to get the serializer for.
* @param <T> the type of the class.
* @return the standard serializer for the given type or null if non-existent.
*/
@Nullable
@SuppressWarnings("unchecked")
private <T> Serializer<T> getSerializer(Class<T> serializableClass) {
Supplier<Serializer> serializer = serializers.get(serializableClass);
return serializer != null ? serializer.get() : null;
}
/**
* Retrieves the override serializer for the specific given type. This is the first serializer choice for a given
* class except in rare circumstances.
*
* @see #getClassSerializer(Class)
*
* @param serializableClass the class to get the serializer for.
* @param <T> the type of the class.
* @return the override serializer for the given type or null if non-existent.
*/
@Nullable
@SuppressWarnings("unchecked")
private <T> Serializer<T> getOverrideSerializer(Class<T> serializableClass) {
Supplier<Serializer> serializer = overrideSerializers.get(serializableClass);
return serializer != null ? serializer.get() : null;
}
/**
* Retrieves the fallback serializer, which is used when no other appropriate serializer exists.
*
* @return the fallback serializer for this SerializerSet.
*/
@NotNull
public Serializer getFallbackSerializer() {
return fallbackSerializer.get();
}
/**
* Tries to create and store an instance of the given serializer class. This will only work for serializers with a
* 0-arg constructor. If there is not a 0-arg constructor, nothing happens. Generally, {@link #registerSerializerInstance(Class, Serializer)}
* should be used instead.
*/
private <S extends Serializer> void tryRegisterSerializer(@NotNull Class<S> serializerClass) {
try {
S serializer = createInstance(serializerClass);
registerSerializerInstance(serializerClass, serializer);
} catch (Exception ignore) { }
}
@NotNull
private <S extends Serializer> S createInstance(@NotNull Class<S> serializerClass) throws Exception {
Constructor<S> constructor = serializerClass.getDeclaredConstructor();
boolean accessible = constructor.isAccessible();
if (!accessible) {
constructor.setAccessible(true);
}
S serializer = constructor.newInstance();
registerSerializerInstance(serializerClass, serializer);
if (!accessible) {
constructor.setAccessible(false);
}
return serializer;
}
/**
* Registers a global instance of a given Serializer class for use with {@link SerializeWith}.
* <p/>
* For a class to be serialized by the serializer specified in {@link SerializeWith} the serializer must have a
* registered global instance. If the serializer has a 0-arg constructor, a global instance will be created
* automatically. This method allows for the registering of serializer instances for serializers without 0-arg
* constructors.
* <p/>
* If the given serializer class has already been registered this method will <strong>replace</strong> the previous
* global instance with the given instance.
*
* @param serializerClass the serializer class to register a global instance of.
* @param serializer the instance of the serializer class to use as the global instance.
* @param <T> the serializer class type.
*/
private <T extends Serializer> void registerSerializerInstance(@NotNull Class<T> serializerClass, @NotNull T serializer) {
serializeWithSerializers.put(serializerClass, () -> serializer);
}
private static final Serializer DEFAULT_SERIALIZER = new DefaultSerializer();
private static final SerializerSet DEFAULT_SET;
static {
Map<Class, Supplier<Serializer>> serializers = new HashMap<>();
// serializer;
final Serializer numberSerializer = new NumberSerializer();
serializers.put(Integer.class, () -> numberSerializer);
serializers.put(Long.class, () -> numberSerializer);
serializers.put(Double.class, () -> numberSerializer);
serializers.put(Float.class, () -> numberSerializer);
serializers.put(Byte.class, () -> numberSerializer);
serializers.put(Short.class, () -> numberSerializer);
final Serializer bigNumberSerializer = new BigNumberSerializer();
serializers.put(BigInteger.class, () -> bigNumberSerializer);
serializers.put(BigDecimal.class, () -> bigNumberSerializer);
final Serializer atomicIntegerSerializer = new AtomicIntegerSerializer();
serializers.put(AtomicInteger.class, () -> atomicIntegerSerializer);
final Serializer atomicLongSerializer = new AtomicLongSerializer();
serializers.put(AtomicLong.class, () -> atomicLongSerializer);
final Serializer booleanSerializer = new BooleanSerializer();
serializers.put(Boolean.class, () -> booleanSerializer);
final Serializer characterSerializer = new CharacterSerializer();
serializers.put(Character.class, () -> characterSerializer);
final Serializer stringSerializer = new StringSerializer();
serializers.put(String.class, () -> stringSerializer);
final Serializer enumSerializer = new EnumSerializer();
serializers.put(Enum.class, () -> enumSerializer);
final Serializer uuidSerializer = new UUIDSerializer();
serializers.put(UUID.class, () -> uuidSerializer);
final Serializer fauxEnumSerializer = new FauxEnumSerializer();
serializers.put(FauxEnum.class, () -> fauxEnumSerializer);
final Serializer collectionSerializer = new CollectionSerializer();
serializers.put(Collection.class, () -> collectionSerializer);
final Serializer mapSerializer = new MapSerializer();
serializers.put(Map.class, () -> mapSerializer);
final Serializer localeSerializer = new LocaleSerializer();
serializers.put(Locale.class, () -> localeSerializer);
final Serializer arraySerializer = new ArraySerializer();
serializers.put(Array.class, () -> arraySerializer);
Map<Predicate<Class<?>>, Class> inheritanceReplacements = new LinkedHashMap<>(3);
inheritanceReplacements.put(c -> c.isAnnotationPresent(FauxEnum.class), FauxEnum.class);
inheritanceReplacements.put(Collection.class::isAssignableFrom, Collection.class);
inheritanceReplacements.put(Map.class::isAssignableFrom, Map.class);
inheritanceReplacements.put(Enum.class::isAssignableFrom, Enum.class);
inheritanceReplacements.put(Class::isArray, Array.class);
DEFAULT_SET = new SerializerSet(serializers, new HashMap<>(), () -> DEFAULT_SERIALIZER, new HashMap<>(), inheritanceReplacements);
}
}