Skip to content

Commit fc4ed95

Browse files
committed
Fixed XItemStack serialization system
1 parent 889108e commit fc4ed95

File tree

4 files changed

+155
-78
lines changed

4 files changed

+155
-78
lines changed

core/src/main/java/com/cryptomorin/xseries/XItemStack.java

Lines changed: 56 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -181,12 +181,19 @@ private static <T extends SerialObject> void recursiveMetaHandle(T serialObject,
181181
List<Function<T, MetaHandler<ItemMeta>>> collectedHandlers) {
182182
Optional<Function<T, MetaHandler<ItemMeta>>> handler = map.get(metaClass);
183183
if (handler != null) {
184-
if (handler.isPresent()) handler.get().apply(serialObject).handle(meta);
184+
if (handler.isPresent()) {
185+
if (collectedHandlers != null) collectedHandlers.add(handler.get());
186+
handler.get().apply(serialObject).handle(meta);
187+
}
188+
185189
return;
186190
}
187191

188-
// This rarely happens. For example:
192+
// This rarely happens for the interface classes themselves For example:
189193
// ColorableArmorMeta extends ArmorMeta, LeatherArmorMeta
194+
// But practically, this will always happen for every metadata, since this
195+
// will be the Craft class that implements the metadata.
196+
190197
List<Function<T, MetaHandler<ItemMeta>>> subCollectedHandlers = new ArrayList<>();
191198
Class<?> superclass = metaClass.getSuperclass();
192199
if (superclass != null) recursiveMetaHandle(serialObject, superclass, meta, map, subCollectedHandlers);
@@ -195,16 +202,17 @@ private static <T extends SerialObject> void recursiveMetaHandle(T serialObject,
195202
}
196203

197204
if (subCollectedHandlers.isEmpty()) {
198-
DESERIALIZE_META_HANDLERS.put((Class<? extends ItemMeta>) metaClass, Optional.empty());
205+
map.put((Class<? extends ItemMeta>) metaClass, Optional.empty());
199206
} else {
200-
DESERIALIZE_META_HANDLERS.put((Class<? extends ItemMeta>) metaClass, Optional.of(inst -> subMeta -> { // Cool syntax!
207+
map.put((Class<? extends ItemMeta>) metaClass, Optional.of(inst -> subMeta -> { // Cool syntax!
201208
T castedInst = cast(inst);
202209
for (Function<T, MetaHandler<ItemMeta>> subCollectedHandler : subCollectedHandlers) {
203210
subCollectedHandler.apply(castedInst).handle(subMeta);
204211
}
205212
}));
206213

207-
collectedHandlers.addAll(subCollectedHandlers);
214+
if (collectedHandlers != null)
215+
collectedHandlers.addAll(subCollectedHandlers);
208216
}
209217
}
210218

@@ -443,7 +451,7 @@ public void handle() {
443451
handleAttributes(meta);
444452
legacySpawnEgg();
445453

446-
recursiveMetaHandle(this, meta.getClass(), meta, SERIALIZE_META_HANDLERS, Collections.emptyList());
454+
recursiveMetaHandle(this, meta.getClass(), meta, SERIALIZE_META_HANDLERS, null);
447455
}
448456

449457
@SuppressWarnings("deprecation")
@@ -724,7 +732,7 @@ private Deserializer(ItemStack item,
724732
public ItemStack parse() {
725733
handleMaterial();
726734
handleDamage();
727-
createMeta();
735+
getOrCreateMeta();
728736
handleDurability();
729737
displayName();
730738
unbreakable();
@@ -735,7 +743,7 @@ public ItemStack parse() {
735743
attributes();
736744
legacySpawnEgg();
737745

738-
recursiveMetaHandle(this, meta.getClass(), meta, DESERIALIZE_META_HANDLERS, Collections.emptyList());
746+
recursiveMetaHandle(this, meta.getClass(), meta, DESERIALIZE_META_HANDLERS, null);
739747

740748
item.setItemMeta(meta);
741749
return item;
@@ -868,44 +876,50 @@ private void enchants() {
868876
}
869877
}
870878

879+
/**
880+
* In older versions, an empty string for a lore line was completely
881+
* ignored, so at least a space " " was needed to get empty lore lines.
882+
*/
883+
private static final boolean SPACE_EMPTY_LORE_LINES = !supports(15);
884+
871885
private void lore() {
872-
if (config.isSet("lore")) {
873-
List<String> translatedLore;
874-
List<String> lores = config.getStringList("lore");
875-
if (!lores.isEmpty()) {
876-
translatedLore = new ArrayList<>(lores.size());
877-
878-
for (String lore : lores) {
879-
if (lore.isEmpty()) {
886+
if (!config.isSet("lore")) return;
887+
888+
List<String> translatedLore;
889+
List<String> lores = config.getStringList("lore");
890+
if (!lores.isEmpty()) {
891+
translatedLore = new ArrayList<>(lores.size());
892+
893+
for (String lore : lores) {
894+
if (SPACE_EMPTY_LORE_LINES && lore.isEmpty()) {
895+
translatedLore.add(" ");
896+
continue;
897+
}
898+
899+
for (String singleLore : splitNewLine(lore)) {
900+
if (SPACE_EMPTY_LORE_LINES && singleLore.isEmpty()) {
880901
translatedLore.add(" ");
881902
continue;
882903
}
883-
884-
for (String singleLore : splitNewLine(lore)) {
885-
if (singleLore.isEmpty()) {
886-
translatedLore.add(" ");
887-
continue;
888-
}
889-
translatedLore.add(translator.apply(singleLore));
890-
}
904+
translatedLore.add(translator.apply(singleLore));
891905
}
892-
} else {
893-
String lore = config.getString("lore");
894-
translatedLore = new ArrayList<>(10);
895-
896-
if (!Strings.isNullOrEmpty(lore)) {
897-
for (String singleLore : splitNewLine(lore)) {
898-
if (singleLore.isEmpty()) {
899-
translatedLore.add(" ");
900-
continue;
901-
}
902-
translatedLore.add(translator.apply(singleLore));
906+
}
907+
} else {
908+
String lore = config.getString("lore");
909+
translatedLore = new ArrayList<>(10);
910+
911+
if (!Strings.isNullOrEmpty(lore)) {
912+
for (String singleLore : splitNewLine(lore)) {
913+
if (SPACE_EMPTY_LORE_LINES && singleLore.isEmpty()) {
914+
translatedLore.add(" ");
915+
continue;
903916
}
917+
translatedLore.add(translator.apply(singleLore));
904918
}
905919
}
906-
907-
meta.setLore(translatedLore);
908920
}
921+
922+
meta.setLore(translatedLore);
909923
}
910924

911925
@SuppressWarnings("deprecation")
@@ -967,8 +981,8 @@ private void handleAxolotlBucketMeta(AxolotlBucketMeta bucket) {
967981

968982
@SuppressWarnings("UnstableApiUsage")
969983
private void handleArmorMeta(ArmorMeta armor) {
970-
if (config.isSet("trim")) {
971-
ConfigurationSection trim = config.getConfigurationSection("trim");
984+
ConfigurationSection trim = config.getConfigurationSection("trim");
985+
if (trim != null) {
972986
TrimMaterial trimMaterial = Registry.TRIM_MATERIAL.get(NamespacedKey.fromString(trim.getString("material")));
973987
TrimPattern trimPattern = Registry.TRIM_PATTERN.get(NamespacedKey.fromString(trim.getString("pattern")));
974988
armor.setTrim(new ArmorTrim(trimMaterial, trimPattern));
@@ -1205,14 +1219,12 @@ private void handleDamage() {
12051219
if (amount > 1) item.setAmount(amount);
12061220
}
12071221

1208-
private void createMeta() {
1209-
ItemMeta tempMeta = item.getItemMeta();
1210-
if (tempMeta == null) {
1222+
private void getOrCreateMeta() {
1223+
meta = item.getItemMeta();
1224+
if (meta == null) {
12111225
// When AIR is null. Useful for when you just want to use the meta to save data and
12121226
// set the type later. A simple CraftMetaItem.
12131227
meta = Bukkit.getItemFactory().getItemMeta(XMaterial.STONE.get());
1214-
} else {
1215-
meta = tempMeta;
12161228
}
12171229
}
12181230

core/src/main/java/com/cryptomorin/xseries/base/XRegistry.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,9 @@ public final class XRegistry<XForm extends XBase<XForm, BukkitForm>, BukkitForm>
7979
private static boolean ensureLoaded = false;
8080

8181
/**
82-
* This method forces the static initialization of {@link XBase} classes
83-
* that have a {@link Registry}.
82+
* Forces the static initialization of {@link XBase} classes
83+
* that have a {@link Registry} so that their registry get registered
84+
* by the {@link #registerModule(XRegistry, Class)} in the constructor.
8485
* <p>
8586
* This practically should not be needed, but we include it anyway just
8687
* to make sure it works for unexpected cases.
@@ -97,27 +98,32 @@ private static void ensureLoadedRegistries() {
9798
XEntityType.REGISTRY.getClass();
9899
XEnchantment.REGISTRY.getClass();
99100
XParticle.REGISTRY.getClass();
101+
100102
ensureLoaded = true;
101103
}
102104

103105
/**
104106
* Gets the registry associated to a {@link XBase} class.
107+
* Usually this method is used for serialization purposes
108+
* since the generic information of the class isn't always known.
105109
*
106110
* @see #registryOf(Class)
107111
*/
108112
@Nullable
109-
public static XRegistry<?, ?> unsafeRegistryOf(Class<?> clazz) {
113+
@ApiStatus.Experimental
114+
public static XRegistry<?, ?> rawRegistryOf(Class<?> clazz) {
110115
ensureLoadedRegistries();
111116
return REGISTRIES.get(clazz);
112117
}
113118

114119
/**
115120
* Gets the registry associated to a {@link XBase} class.
116121
*
117-
* @see #unsafeRegistryOf(Class)
122+
* @see #rawRegistryOf(Class)
118123
*/
119124
@SuppressWarnings("unchecked")
120125
@Nullable
126+
@ApiStatus.Experimental
121127
public static <XForm extends XBase<XForm, BukkitForm>, BukkitForm> XRegistry<XForm, BukkitForm> registryOf(Class<? extends XForm> clazz) {
122128
ensureLoadedRegistries();
123129
return (XRegistry<XForm, BukkitForm>) REGISTRIES.get(clazz);

core/src/test/com/cryptomorin/xseries/test/XSeriesTests.java

Lines changed: 79 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@
5353
import org.bukkit.entity.Painting;
5454
import org.bukkit.entity.Player;
5555
import org.bukkit.inventory.ItemStack;
56+
import org.bukkit.inventory.meta.ColorableArmorMeta;
5657
import org.bukkit.inventory.meta.ItemMeta;
58+
import org.bukkit.inventory.meta.trim.ArmorTrim;
59+
import org.bukkit.inventory.meta.trim.TrimMaterial;
60+
import org.bukkit.inventory.meta.trim.TrimPattern;
5761
import org.bukkit.potion.PotionEffectType;
5862
import org.bukkit.potion.PotionType;
5963
import org.junit.jupiter.api.Assertions;
@@ -130,9 +134,9 @@ private static Location getCenterOfChunk(Chunk c) {
130134

131135
private static void testRegistry() {
132136
assertNotNull(XRegistry.registryOf(XSound.class));
133-
assertNotNull(XRegistry.unsafeRegistryOf(XSound.class));
137+
assertNotNull(XRegistry.rawRegistryOf(XSound.class));
134138
assertNotNull(XRegistry.registryOf(XAttribute.class));
135-
assertNotNull(XRegistry.unsafeRegistryOf(XAttribute.class));
139+
assertNotNull(XRegistry.rawRegistryOf(XAttribute.class));
136140
}
137141

138142
private static void wrapperTest() {
@@ -390,76 +394,131 @@ private static void assertMaterial(XMaterial original, Material expect) {
390394
Assertions.assertSame(XMaterial.matchXMaterial(selfNameMapped.get().parseItem()), original);
391395
}
392396

397+
private static final class ItemSerialDual {
398+
private ItemStack serialized, deserialized;
399+
}
400+
393401
private static void testXItemStack() {
402+
Map<String, ItemSerialDual> map = new HashMap<>();
403+
YamlConfiguration serializeConfig;
394404
log("Testing XItemStack...");
405+
395406
try {
396-
serializeItemStack();
397-
deserializeItemStack();
407+
serializeConfig = serializeItemStack(map);
408+
deserializeItemStack(map);
398409
} catch (IOException | InvalidConfigurationException e) {
399410
throw new AssertionFailedError("Failed to serialize/deserialize items", e);
400411
}
412+
413+
for (Map.Entry<String, ItemSerialDual> entry : map.entrySet()) {
414+
ItemSerialDual dual = entry.getValue();
415+
if (dual.serialized == null || dual.deserialized == null) {
416+
log("Either serialized and deserialized doesn't exist for: " + entry.getKey());
417+
} else {
418+
assertTrue(dual.serialized.isSimilar(dual.deserialized),
419+
() -> "Items for '" + entry.getKey() + "' are not similar:\n\nSerialized: "
420+
+ dual.serialized + "\n\nDeserialized: " + dual.deserialized + '\n');
421+
422+
ConfigurationSection serializeRedeserialized = serializeConfig.getConfigurationSection(entry.getKey());
423+
ItemStack redeserializedItem = XItemStack.deserialize(serializeRedeserialized);
424+
425+
assertTrue(dual.serialized.isSimilar(redeserializedItem),
426+
() -> "Items for redeserialized '" + entry.getKey() + "' are not similar:\n\nSerialized: "
427+
+ dual.serialized + "\n\nDeserialized: " + redeserializedItem + '\n');
428+
}
429+
}
401430
}
402431

403-
private static void deserializeItemStack() throws IOException, InvalidConfigurationException {
432+
private static void deserializeItemStack(Map<String, ItemSerialDual> map) throws IOException, InvalidConfigurationException {
404433
YamlConfiguration yaml = new YamlConfiguration();
405434
yaml.load(ResourceHelper.getResourceAsFile("itemstack.yml"));
406435

407436
for (String section : yaml.getKeys(false)) {
408437
ConfigurationSection itemSection = yaml.getConfigurationSection(section);
409438
ItemStack item = XItemStack.deserialize(itemSection);
410-
log("[Item] " + section + ": " + item);
439+
440+
map.compute(section, (k, v) -> {
441+
if (v == null) v = new ItemSerialDual();
442+
v.deserialized = item;
443+
return v;
444+
});
445+
log("[Deserialized Item] " + section + ": " + item);
411446
}
412447
}
413448

414-
private static ItemStack createItem(XMaterial material, String name, Consumer<ItemMeta> metaConsumer) {
449+
private static ItemStack createItem(XMaterial material, Consumer<ItemMeta> metaConsumer) {
415450
ItemStack item = material.parseItem();
416451
ItemMeta meta = item.getItemMeta();
417-
meta.setDisplayName(name);
418452
metaConsumer.accept(meta);
419453
item.setItemMeta(meta);
420454
return item;
421455
}
422456

457+
private static boolean metaExists(String className) {
458+
try {
459+
Class.forName("org.bukkit.inventory.meta." + className);
460+
return true;
461+
} catch (ClassNotFoundException ignored) {
462+
return false;
463+
}
464+
}
465+
423466
@SuppressWarnings("CodeBlock2Expr")
424-
private static void serializeItemStack() throws IOException {
467+
private static YamlConfiguration serializeItemStack(Map<String, ItemSerialDual> map) throws IOException {
425468
File file = new File(Bukkit.getWorldContainer(), "serialized.yml");
426469
if (!file.exists()) {
427470
file.getParentFile().mkdirs();
428471
file.createNewFile();
429472
}
430473

431-
List<ItemStack> items = new ArrayList<>();
432-
items.add(createItem(XMaterial.DIAMOND, "Diamonds", meta -> {
433-
meta.setLore(Arrays.asList("Line 1", "", "Line 2"));
474+
Map<String, ItemStack> items = new HashMap<>();
475+
476+
items.put("sword", createItem(XMaterial.DIAMOND_SWORD, meta -> {
477+
meta.setDisplayName("&3Yay");
478+
meta.setLore(Arrays.asList("Line 1", "Line 2", " ", "Line 4"));
434479
}));
480+
if (metaExists("ColorableArmorMeta")) {
481+
items.put("leather-colored-armor-trim", createItem(XMaterial.LEATHER_CHESTPLATE, meta -> {
482+
ColorableArmorMeta leather = (ColorableArmorMeta) meta;
483+
leather.setColor(Color.fromRGB(255, 155, 155));
484+
leather.setTrim(new ArmorTrim(TrimMaterial.DIAMOND, TrimPattern.SNOUT));
485+
}));
486+
}
435487
if (Constants.TEST_MOJANG_API) {
436-
items.add(createItem(XMaterial.PLAYER_HEAD, "head-notch", meta -> {
488+
items.put("head-notch", createItem(XMaterial.PLAYER_HEAD, meta -> {
437489
XSkull.of(meta).profile(
438490
Profileable.username("Notch")
439491
.transform(ProfileTransformer.includeOriginalValue())
440492
).apply();
441493
}));
442-
items.add(createItem(XMaterial.PLAYER_HEAD, "head-uuid", meta -> {
494+
items.put("head-uuid", createItem(XMaterial.PLAYER_HEAD, meta -> {
443495
XSkull.of(meta).profile(
444496
Profileable.of(UUID.fromString("45d3f688-0765-4725-b5dd-dbc28fdfc9ab"))
445497
.transform(ProfileTransformer.includeOriginalValue())
446498
).apply();
447499
}));
448-
items.add(createItem(XMaterial.PLAYER_HEAD, "head-username-no-transform", meta -> {
500+
items.put("head-username-no-transform", createItem(XMaterial.PLAYER_HEAD, meta -> {
449501
XSkull.of(meta).profile(
450502
Profileable.of(UUID.fromString("45d3f688-0765-4725-b5dd-dbc28fdfc9ab"))
451503
).apply();
452504
}));
453505
}
454-
items.add(createItem(XMaterial.PLAYER_HEAD, "no-op head", meta -> {}));
506+
items.put("head-no-op", createItem(XMaterial.PLAYER_HEAD, meta -> {}));
455507

456508
YamlConfiguration yaml = new YamlConfiguration();
457-
for (ItemStack item : items) {
458-
ItemMeta meta = item.getItemMeta();
459-
ConfigurationSection section = yaml.createSection(meta.getDisplayName());
460-
XItemStack.serialize(item, section);
509+
for (Map.Entry<String, ItemStack> item : items.entrySet()) {
510+
String sectionName = item.getKey();
511+
ConfigurationSection section = yaml.createSection(sectionName);
512+
513+
XItemStack.serialize(item.getValue(), section);
514+
map.compute(sectionName, (k, v) -> {
515+
if (v == null) v = new ItemSerialDual();
516+
v.serialized = item.getValue();
517+
return v;
518+
});
461519
}
462520
yaml.save(file);
521+
return yaml;
463522
}
464523

465524
private static void testSkulls() {

0 commit comments

Comments
 (0)