|
|
@@ -1,37 +1,225 @@
|
|
|
package me.lethunderhawk.fluxapi.profile.representation;
|
|
|
|
|
|
+import me.lethunderhawk.fluxapi.profile.event.FluxProfileLoadEvent;
|
|
|
+import me.lethunderhawk.fluxapi.profile.event.FluxProfileUpdateEvent;
|
|
|
+import org.bukkit.Bukkit;
|
|
|
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
|
|
+import org.bukkit.configuration.serialization.ConfigurationSerialization;
|
|
|
import org.jetbrains.annotations.NotNull;
|
|
|
|
|
|
import java.util.HashMap;
|
|
|
import java.util.Map;
|
|
|
+import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
|
|
public class FluxProfile implements ConfigurationSerializable {
|
|
|
|
|
|
+ /* ------------------------------------------------ */
|
|
|
+ /* Static Registry */
|
|
|
+ /* ------------------------------------------------ */
|
|
|
+
|
|
|
+ private static final Map<String, Class<? extends ConfigurationSerializable>> REGISTERED_TYPES = new ConcurrentHashMap<>();
|
|
|
+ private static final Map<String, SubProfileFactory<?>> DEFAULT_FACTORIES = new HashMap<>();
|
|
|
+ /**
|
|
|
+ * Register a sub profile type.
|
|
|
+ *
|
|
|
+ * @param id The yaml key (e.g. "customdata1")
|
|
|
+ * @param clazz The serializable class
|
|
|
+ */
|
|
|
+ public static void registerSubProfile(
|
|
|
+ @NotNull String id,
|
|
|
+ @NotNull Class<? extends ConfigurationSerializable> clazz
|
|
|
+ ) {
|
|
|
+ REGISTERED_TYPES.put(id, clazz);
|
|
|
+ ConfigurationSerialization.registerClass(clazz);
|
|
|
+
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * Register a sub profile type with a default Object.
|
|
|
+ *
|
|
|
+ * @param id The yaml key (e.g. "customdata1")
|
|
|
+ * @param clazz The serializable class
|
|
|
+ * @param factory The default factory used to create a subConfig if there is nothing saved yet
|
|
|
+ */
|
|
|
+ public static <T extends ConfigurationSerializable> void registerSubProfile(
|
|
|
+ @NotNull String id,
|
|
|
+ @NotNull Class<T> clazz,
|
|
|
+ @NotNull SubProfileFactory<T> factory
|
|
|
+ ) {
|
|
|
+ REGISTERED_TYPES.put(id, clazz);
|
|
|
+ DEFAULT_FACTORIES.put(id, factory);
|
|
|
+ ConfigurationSerialization.registerClass(clazz);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* ------------------------------------------------ */
|
|
|
+ /* Instance Data */
|
|
|
+ /* ------------------------------------------------ */
|
|
|
+
|
|
|
private final String playerUUID;
|
|
|
- private final Map<String, ConfigurationSerializable> subConfigs = new HashMap<>();
|
|
|
+ private boolean dirty = false;
|
|
|
+
|
|
|
+ // Fully deserialized objects
|
|
|
+ private final Map<String, ConfigurationSerializable> subProfiles = new HashMap<>();
|
|
|
|
|
|
- public FluxProfile(String playerUUID) {
|
|
|
+ // Raw maps (used if plugin not yet registered)
|
|
|
+ private final Map<String, Map<String, Object>> unresolvedData = new HashMap<>();
|
|
|
+
|
|
|
+ /* ------------------------------------------------ */
|
|
|
+ /* Constructors */
|
|
|
+ /* ------------------------------------------------ */
|
|
|
+
|
|
|
+ public FluxProfile(@NotNull String playerUUID) {
|
|
|
this.playerUUID = playerUUID;
|
|
|
}
|
|
|
|
|
|
- // Deserialization constructor
|
|
|
- public FluxProfile(Map<String, Object> map) {
|
|
|
+ /**
|
|
|
+ * Bukkit deserialization constructor
|
|
|
+ */
|
|
|
+ public FluxProfile(@NotNull Map<String, Object> map) {
|
|
|
|
|
|
this.playerUUID = (String) map.get("uuid");
|
|
|
|
|
|
if (playerUUID == null) {
|
|
|
throw new IllegalStateException("Profile missing UUID.");
|
|
|
}
|
|
|
+
|
|
|
+ for (Map.Entry<String, Object> entry : map.entrySet()) {
|
|
|
+
|
|
|
+ String key = entry.getKey();
|
|
|
+
|
|
|
+ if (key.equals("uuid")) continue;
|
|
|
+
|
|
|
+ Object value = entry.getValue();
|
|
|
+
|
|
|
+ // CASE 1: Already deserialized by Bukkit
|
|
|
+ if (value instanceof ConfigurationSerializable serializable) {
|
|
|
+
|
|
|
+ subProfiles.put(key, serializable);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // CASE 2: Raw map (class not registered yet)
|
|
|
+ if (value instanceof Map<?, ?> rawMap) {
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ Map<String, Object> section =
|
|
|
+ new HashMap<>((Map<String, Object>) rawMap);
|
|
|
+
|
|
|
+ Object typeKey = section.get("==");
|
|
|
+
|
|
|
+ if (typeKey instanceof String className) {
|
|
|
+
|
|
|
+ try {
|
|
|
+ Class<?> clazz = Class.forName(className);
|
|
|
+
|
|
|
+ if (ConfigurationSerializable.class.isAssignableFrom(clazz)) {
|
|
|
+
|
|
|
+ ConfigurationSerializable obj =
|
|
|
+ ConfigurationSerialization.deserializeObject(
|
|
|
+ section,
|
|
|
+ clazz.asSubclass(ConfigurationSerializable.class)
|
|
|
+ );
|
|
|
+
|
|
|
+ subProfiles.put(key, obj);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (ClassNotFoundException ignored) {}
|
|
|
+ }
|
|
|
+
|
|
|
+ unresolvedData.put(key, section);
|
|
|
+ }
|
|
|
+
|
|
|
+ System.out.println("Loaded key: " + key + " type: " + value.getClass().getName());
|
|
|
+ }
|
|
|
+
|
|
|
+ Bukkit.getPluginManager().callEvent(new FluxProfileLoadEvent(this));
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+
|
|
|
+ /* ------------------------------------------------ */
|
|
|
+ /* Public API */
|
|
|
+ /* ------------------------------------------------ */
|
|
|
+
|
|
|
+ public @NotNull String getPlayerUUID() {
|
|
|
+ return playerUUID;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Add or replace a sub profile.
|
|
|
+ */
|
|
|
+ public void addSubProfile(
|
|
|
+ @NotNull String id,
|
|
|
+ @NotNull ConfigurationSerializable profile
|
|
|
+ ) {
|
|
|
+ subProfiles.put(id, profile);
|
|
|
+
|
|
|
+ Bukkit.getPluginManager().callEvent(
|
|
|
+ new FluxProfileUpdateEvent(this, id)
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Type-safe retrieval.
|
|
|
+ * Automatically resolves unresolved raw data if class gets registered later.
|
|
|
+ */
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ public synchronized <T extends ConfigurationSerializable> T getSubProfile(
|
|
|
+ @NotNull String id,
|
|
|
+ @NotNull Class<T> expectedType
|
|
|
+ ) {
|
|
|
+ // Already deserialized
|
|
|
+ ConfigurationSerializable existing = subProfiles.get(id);
|
|
|
+ if (existing != null) {
|
|
|
+ return expectedType.cast(existing);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Raw saved data exists → deserialize FIRST
|
|
|
+ Map<String, Object> raw = unresolvedData.remove(id);
|
|
|
+ if (raw != null) {
|
|
|
+
|
|
|
+ Class<? extends ConfigurationSerializable> clazz = REGISTERED_TYPES.get(id);
|
|
|
+
|
|
|
+ if (clazz != null) {
|
|
|
+ ConfigurationSerializable obj =
|
|
|
+ ConfigurationSerialization.deserializeObject(raw, clazz);
|
|
|
+
|
|
|
+ subProfiles.put(id, obj);
|
|
|
+ return expectedType.cast(obj);
|
|
|
+ } else {
|
|
|
+ // Class not yet registered → put raw back
|
|
|
+ unresolvedData.put(id, raw);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Only if NO SAVED DATA exists → create default
|
|
|
+ SubProfileFactory<?> factory = DEFAULT_FACTORIES.get(id);
|
|
|
+ if (factory != null) {
|
|
|
+
|
|
|
+ T created = (T) factory.create(playerUUID);
|
|
|
+ subProfiles.put(id, created);
|
|
|
+ return created;
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* ------------------------------------------------ */
|
|
|
+ /* Serialization */
|
|
|
+ /* ------------------------------------------------ */
|
|
|
+
|
|
|
@Override
|
|
|
public @NotNull Map<String, Object> serialize() {
|
|
|
|
|
|
Map<String, Object> data = new HashMap<>();
|
|
|
data.put("uuid", playerUUID);
|
|
|
- data.put("ein", 1);
|
|
|
- data.putAll(subConfigs);
|
|
|
+
|
|
|
+ // Store resolved profiles
|
|
|
+ data.putAll(subProfiles);
|
|
|
+
|
|
|
+ // Preserve unresolved raw data
|
|
|
+ data.putAll(unresolvedData);
|
|
|
|
|
|
return data;
|
|
|
}
|
|
|
@@ -39,8 +227,4 @@ public class FluxProfile implements ConfigurationSerializable {
|
|
|
public static FluxProfile deserialize(Map<String, Object> map) {
|
|
|
return new FluxProfile(map);
|
|
|
}
|
|
|
-
|
|
|
- public String getPlayerUUID() {
|
|
|
- return playerUUID;
|
|
|
- }
|
|
|
}
|