diff --git a/jme3-core/src/main/java/com/jme3/scene/UserData.java b/jme3-core/src/main/java/com/jme3/scene/UserData.java index 2fb04f44d3..a873a6135f 100644 --- a/jme3-core/src/main/java/com/jme3/scene/UserData.java +++ b/jme3-core/src/main/java/com/jme3/scene/UserData.java @@ -74,8 +74,20 @@ public final class UserData implements Savable { private static final int TYPE_SHORT = 10; private static final int TYPE_BYTE = 11; + // Counter for generating unique IDs to prevent field name collisions + private static final java.util.concurrent.atomic.AtomicLong uniqueIdCounter = + new java.util.concurrent.atomic.AtomicLong(0); + + // Prefix used for backwards compatibility with files created before the uniqueId field was added + private static final String LEGACY_PREFIX = "0"; + protected byte type; protected Object value; + + // Unique identifier for this UserData instance to prevent field name collisions + // when multiple UserData objects with lists/maps/arrays are serialized together. + // Marked volatile to ensure visibility across threads during concurrent serialization. + private transient volatile String uniqueId; public UserData() { } @@ -139,6 +151,19 @@ public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(type, "type", (byte) 0); + // Generate a unique ID for this instance to prevent field name collisions + // when multiple UserData objects with lists/maps/arrays are serialized together. + // Store it so it can be read back during deserialization. + // Use double-checked locking to ensure thread-safety. + if (uniqueId == null) { + synchronized (this) { + if (uniqueId == null) { + uniqueId = String.valueOf(uniqueIdCounter.incrementAndGet()); + } + } + } + oc.write(uniqueId, "uniqueId", null); + switch (type) { case TYPE_INTEGER: int i = (Integer) value; @@ -165,15 +190,15 @@ public void write(JmeExporter ex) throws IOException { oc.write(sav, "savableVal", null); break; case TYPE_LIST: - this.writeList(oc, (List) value, "0"); + this.writeList(oc, (List) value, uniqueId + ":0"); break; case TYPE_MAP: Map map = (Map) value; - this.writeList(oc, map.keySet(), "0"); - this.writeList(oc, map.values(), "1"); + this.writeList(oc, map.keySet(), uniqueId + ":0"); + this.writeList(oc, map.values(), uniqueId + ":1"); break; case TYPE_ARRAY: - this.writeList(oc, Arrays.asList((Object[]) value), "0"); + this.writeList(oc, Arrays.asList((Object[]) value), uniqueId + ":0"); break; case TYPE_DOUBLE: Double d = (Double) value; @@ -196,6 +221,16 @@ public void write(JmeExporter ex) throws IOException { public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); type = ic.readByte("type", (byte) 0); + + // Read the unique ID that was written during serialization + uniqueId = ic.readString("uniqueId", null); + + // For backwards compatibility with old files that don't have uniqueId, + // use the original prefix. This matches the field names in old files. + if (uniqueId == null) { + uniqueId = "0"; + } + switch (type) { case TYPE_INTEGER: value = ic.readInt("intVal", 0); @@ -216,19 +251,19 @@ public void read(JmeImporter im) throws IOException { value = ic.readSavable("savableVal", null); break; case TYPE_LIST: - value = this.readList(ic, "0"); + value = this.readList(ic, uniqueId + ":0"); break; case TYPE_MAP: Map map = new HashMap<>(); - List keys = this.readList(ic, "0"); - List values = this.readList(ic, "1"); + List keys = this.readList(ic, uniqueId + ":0"); + List values = this.readList(ic, uniqueId + ":1"); for (int i = 0; i < keys.size(); ++i) { map.put(keys.get(i), values.get(i)); } value = map; break; case TYPE_ARRAY: - value = this.readList(ic, "0").toArray(); + value = this.readList(ic, uniqueId + ":0").toArray(); break; case TYPE_DOUBLE: value = ic.readDouble("doubleVal", 0.); diff --git a/jme3-plugins/src/test/java/com/jme3/export/JmeExporterTest.java b/jme3-plugins/src/test/java/com/jme3/export/JmeExporterTest.java index d11df242cc..beede5de25 100644 --- a/jme3-plugins/src/test/java/com/jme3/export/JmeExporterTest.java +++ b/jme3-plugins/src/test/java/com/jme3/export/JmeExporterTest.java @@ -139,7 +139,7 @@ public void testSaveWithNullParent() throws IOException { public void testExporterConsistency() { // final boolean testXML = true; - final boolean testLists = false; + final boolean testLists = true; final boolean testMaps = true; final boolean printXML = false;