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> REGISTERED_TYPES = new ConcurrentHashMap<>(); private static final Map> 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 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 void registerSubProfile( @NotNull String id, @NotNull Class clazz, @NotNull SubProfileFactory factory ) { REGISTERED_TYPES.put(id, clazz); DEFAULT_FACTORIES.put(id, factory); ConfigurationSerialization.registerClass(clazz); } /* ------------------------------------------------ */ /* Instance Data */ /* ------------------------------------------------ */ private final String playerUUID; private boolean dirty = false; // Fully deserialized objects private final Map subProfiles = new HashMap<>(); // Raw maps (used if plugin not yet registered) private final Map> unresolvedData = new HashMap<>(); /* ------------------------------------------------ */ /* Constructors */ /* ------------------------------------------------ */ public FluxProfile(@NotNull String playerUUID) { this.playerUUID = playerUUID; } /** * Bukkit deserialization constructor */ public FluxProfile(@NotNull Map map) { this.playerUUID = (String) map.get("uuid"); if (playerUUID == null) { throw new IllegalStateException("Profile missing UUID."); } for (Map.Entry 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 section = new HashMap<>((Map) 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 getSubProfile( @NotNull String id, @NotNull Class expectedType ) { // Already deserialized ConfigurationSerializable existing = subProfiles.get(id); if (existing != null) { return expectedType.cast(existing); } // Raw saved data exists → deserialize FIRST Map raw = unresolvedData.remove(id); if (raw != null) { Class 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 serialize() { Map data = new HashMap<>(); data.put("uuid", playerUUID); // Store resolved profiles data.putAll(subProfiles); // Preserve unresolved raw data data.putAll(unresolvedData); return data; } public static FluxProfile deserialize(Map map) { return new FluxProfile(map); } }