package io.github.lucaseasedup.logit.persistence; import io.github.lucaseasedup.logit.LogItCoreObject; import io.github.lucaseasedup.logit.account.Account; import io.github.lucaseasedup.logit.common.ReportedException; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.bukkit.entity.Player; /** * Provides a centralized persistence serialization platform. */ public final class PersistenceManager extends LogItCoreObject { /** * Serializes player data using the specified persistence serializer * * <p> No action will be taken if the player's data * has already been serialized using the specified serializer. * * @param account the account from which persistence data will be read. * @param player the player whose data will be serialized. * @param clazz the serializer class. * * @throws IllegalArgumentException if {@code account}, {@code player} * or {@code clazz} is {@code null}. * * @throws ReportedException if an I/O error occurred, * and it was reported to the logger. */ public void serializeUsing( Account account, Player player, Class<? extends PersistenceSerializer> clazz ) { if (account == null || player == null || clazz == null) throw new IllegalArgumentException(); PersistenceSerializer serializer = getSerializer(clazz); if (serializer == null) return; serializeUsing(account, player, serializer); } public void serializeUsing( Account account, Player player, PersistenceSerializer serializer ) { if (account == null || player == null || serializer == null) throw new IllegalArgumentException(); Map<String, String> persistence = account.getPersistence(); if (persistence == null) return; if (!isSerializedUsing(persistence, serializer)) { serializer.serialize(persistence, player); account.savePersistence(persistence); } } /** * Serializes player data using all enabled serializers registered * using the {@link #registerSerializer} method. * * @param account the account from which persistence data will be read. * @param player the player whose data will be serialized. * * @throws IllegalArgumentException if {@code account} or * {@code player} is {@code null}. * * @throws ReportedException if an I/O error occurred, * and it was reported to the logger. */ public void serialize(Account account, Player player) { if (account == null || player == null) throw new IllegalArgumentException(); Map<String, String> persistence = account.getPersistence(); if (persistence == null) return; for (Class<? extends PersistenceSerializer> clazz : getSerializersInOrder()) { PersistenceSerializer serializer = getSerializer(clazz); if (serializer == null) continue; if (!isSerializedUsing(persistence, serializer)) { serializer.serialize(persistence, player); } } account.savePersistence(persistence); } /** * Unserializes player data using the specified persistence serializer * * <p> No action will be taken if the player's data * has not been serialized using the specified serializer. * * @param account the account from which persistence data will be read. * @param player the player whose data will be unserialized. * @param clazz the serializer class. * * @throws IllegalArgumentException if {@code account}, {@code player} or * {@code clazz} is {@code null}. * * @throws ReportedException if an I/O error occurred, * and it was reported to the logger. */ public void unserializeUsing( Account account, Player player, Class<? extends PersistenceSerializer> clazz ) { if (account == null || player == null || clazz == null) throw new IllegalArgumentException(); PersistenceSerializer serializer = getSerializer(clazz); if (serializer == null) return; unserializeUsing(account, player, serializer); } public void unserializeUsing( Account account, Player player, PersistenceSerializer serializer ) { if (account == null || player == null || serializer == null) throw new IllegalArgumentException(); Map<String, String> persistence = account.getPersistence(); if (persistence == null) return; if (isSerializedUsing(persistence, serializer)) { serializer.unserialize(persistence, player); for (Key key : getSerializerKeys(serializer.getClass())) { persistence.put(key.name(), key.defaultValue()); } account.savePersistence(persistence); } } /** * Unserializes player data using all enabled serializers registered * using the {@link #registerSerializer} method. * * @param account the account from which persistence data will be read. * @param player the player whose data will be unserialized. * * @throws IllegalArgumentException if {@code account} or * {@code player} is {@code null}. * * @throws ReportedException if an I/O error occurred, * and it was reported to the logger. */ public void unserialize(Account account, Player player) { if (account == null || player == null) throw new IllegalArgumentException(); Map<String, String> persistence = account.getPersistence(); if (persistence == null) return; Set<Key> keysToErase = new HashSet<>(); for (Class<? extends PersistenceSerializer> clazz : getSerializersInOrder()) { PersistenceSerializer serializer = getSerializer(clazz); if (serializer == null) continue; if (isSerializedUsing(persistence, serializer)) { Collections.addAll( keysToErase, getSerializerKeys(serializer.getClass()) ); serializer.unserialize(persistence, player); } } for (Key key : keysToErase) { persistence.put(key.name(), key.defaultValue()); } account.savePersistence(persistence); } /** * Registers a serializer class. * * @param clazz the serializer class to be registered. * * @return {@code false} if the serializer class was already registered; * {@code true} otherwise. * * @throws ReflectiveOperationException if serializer constructor invocation failed. * @throws IllegalArgumentException if {@code clazz} is {@code null}. * */ public boolean registerSerializer( Class<? extends PersistenceSerializer> clazz ) throws ReflectiveOperationException { if (clazz == null) throw new IllegalArgumentException(); if (serializers.containsKey(clazz)) return false; serializers.put(clazz, constructSerializer(clazz)); return true; } /** * Unregisters a serializer class. * * @param clazz the serializer class to be unregistered. * * @return {@code false} if the serializer class was not registered; * {@code true} otherwise. * * @throws IllegalArgumentException if {@code clazz} is {@code null}. */ public boolean unregisterSerializer( Class<? extends PersistenceSerializer> clazz ) { if (clazz == null) throw new IllegalArgumentException(); if (!serializers.containsKey(clazz)) return false; serializers.remove(clazz); return true; } /** * Returns an instance of a serializer based on the provided class. * * @param clazz the serializer class. * * @return the serializer instance, or {@code null} * if this serializer class has not been * registered in this {@code PersistenceManager}. * * @throws IllegalArgumentException if {@code clazz} is {@code null}. */ public PersistenceSerializer getSerializer( Class<? extends PersistenceSerializer> clazz ) { if (clazz == null) throw new IllegalArgumentException(); return serializers.get(clazz); } private boolean isSerializedUsing( Map<String, String> persistence, PersistenceSerializer serializer ) { if (persistence == null || serializer == null) throw new IllegalArgumentException(); for (Key key : getSerializerKeys(serializer.getClass())) { String value = persistence.get(key.name()); switch (key.constraint()) { case NONE: { break; } case NON_NULL: { if (value == null) { return false; } break; } case NOT_EMPTY: { if (value == null || value.isEmpty()) { return false; } break; } case NOT_BLANK: { if (StringUtils.isBlank(value)) { return false; } break; } default: { throw new RuntimeException( "Unsupported key constraint: " + key.constraint() ); } } } return true; } private Class<? extends PersistenceSerializer>[] getSerializersInOrder() { Set<Class<? extends PersistenceSerializer>> classes = serializers.keySet(); @SuppressWarnings("unchecked") Class<? extends PersistenceSerializer>[] result = classes.toArray(new Class[classes.size()]); Arrays.sort(result, new Comparator<Class<? extends PersistenceSerializer>>() { @Override public int compare(Class<? extends PersistenceSerializer> o1, Class<? extends PersistenceSerializer> o2) { if (o1 == o2) return 0; Before o1BeforeAnnotation = o1.getAnnotation(Before.class); After o1AfterAnnotation = o1.getAnnotation(After.class); Before o2BeforeAnnotation = o2.getAnnotation(Before.class); After o2AfterAnnotation = o2.getAnnotation(After.class); Class<? extends PersistenceSerializer> o1Before = (o1BeforeAnnotation != null) ? o1BeforeAnnotation.value() : null; Class<? extends PersistenceSerializer> o1After = (o1AfterAnnotation != null) ? o1AfterAnnotation.value() : null; Class<? extends PersistenceSerializer> o2Before = (o2BeforeAnnotation != null) ? o2BeforeAnnotation.value() : null; Class<? extends PersistenceSerializer> o2After = (o2AfterAnnotation != null) ? o2AfterAnnotation.value() : null; if (o1Before == o1After && (o1Before != null || o1After != null)) throw new RuntimeException(); if (o2Before == o2After && (o2Before != null || o2After != null)) throw new RuntimeException(); if (o1Before == o2Before && (o1Before != null || o2Before != null)) throw new RuntimeException("Circular serializer dependency"); if (o1After == o2After && (o1After != null || o2After != null)) throw new RuntimeException("Circular serializer dependency"); if (o1Before == o2) return -1; if (o1After == o2) return 1; if (o2Before == o1) return 1; if (o2After == o1) return -1; return 0; } }); return result; } private Key[] getSerializerKeys( Class<? extends PersistenceSerializer> clazz ) { if (clazz == null) throw new IllegalArgumentException(); Keys keys = clazz.getAnnotation(Keys.class); if (keys == null) return new Key[0]; return keys.value(); } private boolean containsKey( Class<? extends PersistenceSerializer> clazz, String keyName ) { if (clazz == null || keyName == null) throw new IllegalArgumentException(); Key[] keys = getSerializerKeys(clazz); for (Key key : keys) { if (key.name().equals(keyName)) { return true; } } return false; } private static PersistenceSerializer constructSerializer( Class<? extends PersistenceSerializer> clazz ) throws ReflectiveOperationException { if (clazz == null) throw new IllegalArgumentException(); return clazz.getConstructor().newInstance(); } private final Map<Class<? extends PersistenceSerializer>, PersistenceSerializer> serializers = new HashMap<>(); }