Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 43 additions & 8 deletions jme3-core/src/main/java/com/jme3/scene/UserData.java
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
}
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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);
Expand All @@ -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<Object, Object> 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.);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down