FluxProfile.java 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. package me.lethunderhawk.fluxapi.profile.representation;
  2. import me.lethunderhawk.fluxapi.profile.event.FluxProfileLoadEvent;
  3. import me.lethunderhawk.fluxapi.profile.event.FluxProfileUpdateEvent;
  4. import org.bukkit.Bukkit;
  5. import org.bukkit.configuration.serialization.ConfigurationSerializable;
  6. import org.bukkit.configuration.serialization.ConfigurationSerialization;
  7. import org.jetbrains.annotations.NotNull;
  8. import java.util.HashMap;
  9. import java.util.Map;
  10. import java.util.concurrent.ConcurrentHashMap;
  11. public class FluxProfile implements ConfigurationSerializable {
  12. /* ------------------------------------------------ */
  13. /* Static Registry */
  14. /* ------------------------------------------------ */
  15. private static final Map<String, Class<? extends ConfigurationSerializable>> REGISTERED_TYPES = new ConcurrentHashMap<>();
  16. private static final Map<String, SubProfileFactory<?>> DEFAULT_FACTORIES = new HashMap<>();
  17. /**
  18. * Register a sub profile type.
  19. *
  20. * @param id The yaml key (e.g. "customdata1")
  21. * @param clazz The serializable class
  22. */
  23. public static void registerSubProfile(
  24. @NotNull String id,
  25. @NotNull Class<? extends ConfigurationSerializable> clazz
  26. ) {
  27. REGISTERED_TYPES.put(id, clazz);
  28. ConfigurationSerialization.registerClass(clazz);
  29. }
  30. /**
  31. * Register a sub profile type with a default Object.
  32. *
  33. * @param id The yaml key (e.g. "customdata1")
  34. * @param clazz The serializable class
  35. * @param factory The default factory used to create a subConfig if there is nothing saved yet
  36. */
  37. public static <T extends ConfigurationSerializable> void registerSubProfile(
  38. @NotNull String id,
  39. @NotNull Class<T> clazz,
  40. @NotNull SubProfileFactory<T> factory
  41. ) {
  42. REGISTERED_TYPES.put(id, clazz);
  43. DEFAULT_FACTORIES.put(id, factory);
  44. ConfigurationSerialization.registerClass(clazz);
  45. }
  46. /* ------------------------------------------------ */
  47. /* Instance Data */
  48. /* ------------------------------------------------ */
  49. private final String playerUUID;
  50. private boolean dirty = false;
  51. // Fully deserialized objects
  52. private final Map<String, ConfigurationSerializable> subProfiles = new HashMap<>();
  53. // Raw maps (used if plugin not yet registered)
  54. private final Map<String, Map<String, Object>> unresolvedData = new HashMap<>();
  55. /* ------------------------------------------------ */
  56. /* Constructors */
  57. /* ------------------------------------------------ */
  58. public FluxProfile(@NotNull String playerUUID) {
  59. this.playerUUID = playerUUID;
  60. }
  61. /**
  62. * Bukkit deserialization constructor
  63. */
  64. public FluxProfile(@NotNull Map<String, Object> map) {
  65. this.playerUUID = (String) map.get("uuid");
  66. if (playerUUID == null) {
  67. throw new IllegalStateException("Profile missing UUID.");
  68. }
  69. for (Map.Entry<String, Object> entry : map.entrySet()) {
  70. String key = entry.getKey();
  71. if (key.equals("uuid")) continue;
  72. Object value = entry.getValue();
  73. // CASE 1: Already deserialized by Bukkit
  74. if (value instanceof ConfigurationSerializable serializable) {
  75. subProfiles.put(key, serializable);
  76. continue;
  77. }
  78. // CASE 2: Raw map (class not registered yet)
  79. if (value instanceof Map<?, ?> rawMap) {
  80. @SuppressWarnings("unchecked")
  81. Map<String, Object> section =
  82. new HashMap<>((Map<String, Object>) rawMap);
  83. Object typeKey = section.get("==");
  84. if (typeKey instanceof String className) {
  85. try {
  86. Class<?> clazz = Class.forName(className);
  87. if (ConfigurationSerializable.class.isAssignableFrom(clazz)) {
  88. ConfigurationSerializable obj =
  89. ConfigurationSerialization.deserializeObject(
  90. section,
  91. clazz.asSubclass(ConfigurationSerializable.class)
  92. );
  93. subProfiles.put(key, obj);
  94. continue;
  95. }
  96. } catch (ClassNotFoundException ignored) {}
  97. }
  98. unresolvedData.put(key, section);
  99. }
  100. System.out.println("Loaded key: " + key + " type: " + value.getClass().getName());
  101. }
  102. Bukkit.getPluginManager().callEvent(new FluxProfileLoadEvent(this));
  103. }
  104. /* ------------------------------------------------ */
  105. /* Public API */
  106. /* ------------------------------------------------ */
  107. public @NotNull String getPlayerUUID() {
  108. return playerUUID;
  109. }
  110. /**
  111. * Add or replace a sub profile.
  112. */
  113. public void addSubProfile(
  114. @NotNull String id,
  115. @NotNull ConfigurationSerializable profile
  116. ) {
  117. subProfiles.put(id, profile);
  118. Bukkit.getPluginManager().callEvent(
  119. new FluxProfileUpdateEvent(this, id)
  120. );
  121. }
  122. /**
  123. * Type-safe retrieval.
  124. * Automatically resolves unresolved raw data if class gets registered later.
  125. */
  126. @SuppressWarnings("unchecked")
  127. public synchronized <T extends ConfigurationSerializable> T getSubProfile(
  128. @NotNull String id,
  129. @NotNull Class<T> expectedType
  130. ) {
  131. // Already deserialized
  132. ConfigurationSerializable existing = subProfiles.get(id);
  133. if (existing != null) {
  134. return expectedType.cast(existing);
  135. }
  136. // Raw saved data exists → deserialize FIRST
  137. Map<String, Object> raw = unresolvedData.remove(id);
  138. if (raw != null) {
  139. Class<? extends ConfigurationSerializable> clazz = REGISTERED_TYPES.get(id);
  140. if (clazz != null) {
  141. ConfigurationSerializable obj =
  142. ConfigurationSerialization.deserializeObject(raw, clazz);
  143. subProfiles.put(id, obj);
  144. return expectedType.cast(obj);
  145. } else {
  146. // Class not yet registered → put raw back
  147. unresolvedData.put(id, raw);
  148. return null;
  149. }
  150. }
  151. // Only if NO SAVED DATA exists → create default
  152. SubProfileFactory<?> factory = DEFAULT_FACTORIES.get(id);
  153. if (factory != null) {
  154. T created = (T) factory.create(playerUUID);
  155. subProfiles.put(id, created);
  156. return created;
  157. }
  158. return null;
  159. }
  160. /* ------------------------------------------------ */
  161. /* Serialization */
  162. /* ------------------------------------------------ */
  163. @Override
  164. public @NotNull Map<String, Object> serialize() {
  165. Map<String, Object> data = new HashMap<>();
  166. data.put("uuid", playerUUID);
  167. // Store resolved profiles
  168. data.putAll(subProfiles);
  169. // Preserve unresolved raw data
  170. data.putAll(unresolvedData);
  171. return data;
  172. }
  173. public static FluxProfile deserialize(Map<String, Object> map) {
  174. return new FluxProfile(map);
  175. }
  176. }