Saving/Exporting arrays as user data in nodes

Hello everyone, I tried to save/export a node with some user data. The user data are stored in an Integer-Array (Integer) and set in the node with the setUserData(…,…)-function. When I try to load the .j3o-File with the node in the SceneComposer, then I see the user data of the node just as [Ljava.lang.Object;. The same happened, when I load the .j3o-File with the node by using the assetManager.loadModel(…)-function and print out the user data programmatically ([Ljava.lang.Object;@7a9e1769).

Looking at the BinaryExporter and the JME-Wiki I thought, that this should work and that I could export and load Arrays as user data without any workaround. Is this wrong or am I doing something wrong? Has the export-function changed in some of the last JME-Updates? I tested on JME 3.5.2 and 3.6.1.

For testing I used the following code and depending if I want to save or load I comment the relevant lines in or out:

private final static Integer[] SOME_INTEGER = {16, 17, 19, 22, 26, 29, 31, 32, 30, 27, 23, 19};
private Node node;

public static void main(String[] args){
    MainForGradleGameApp app = new MainForGradleGameApp();
    app.start();
}

@Override
public void simpleInitApp() {
    Box b = new Box(1, 1, 1);
    Geometry geom = new Geometry("Box", b);
    Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    mat.setColor("Color", ColorRGBA.Blue);
    geom.setMaterial(mat);
    node = new Node("Node");
    node.setUserData("MyUserData", SOME_INTEGER);
    rootNode.attachChild(node);
    node.attachChild(geom);

    // loadFile();
}

@Override
public void stop() {
    String userHome = System.getProperty("user.home");
    BinaryExporter exporter = BinaryExporter.getInstance();
    File file = new File(userHome+"/Models/"+"MyModel.j3o");
    try {
      exporter.save(node, file);
    } catch (IOException ex) {
      Logger.getLogger(Main.class.getName()).log(Level.SEVERE, "Error: Failed to save game!", ex);
    }
    super.stop();
}

private void loadFile() {
    String userHome = System.getProperty("user.home");
    assetManager.registerLocator(userHome, FileLocator.class);
    node = (Node)assetManager.loadModel("Models/MyModel.j3o");
    rootNode.attachChild(node);
    for (String key : node.getUserDataKeys()) {
        System.out.println(key + " -> " + node.getUserData(key));
    }
}

If getUserData() is returning an actual Integer array then it will look just like the [Ljava.lang… garbage that you posted. That’s the toString() of an array.

Print the class. Cast it to an array and print the elements, etc…

2 Likes

Thank you very much. Of course I had to do as you said. I don’t know, how I could miss this. So I think, then I have another bug in my application, but have to go on searching.

If someone should have the same question, here is example-code, which could be used instead in the loadFile-function to work as expected in this case:

private void loadFile() {
    String userHome = System.getProperty("user.home");
    assetManager.registerLocator(userHome, FileLocator.class);
    node = (Node)assetManager.loadModel("Models/MyModel.j3o");
    rootNode.attachChild(node);
    for (String key : node.getUserDataKeys()) {
        Object[] someInteger = node.getUserData(key);
        System.out.println("KEY: " + key);
        for (Object object : someInteger) {
            System.out.println("VALUES: " + object);
        }
    }
}

Sorry to be back again to this topic. Maybe I am doing something very wrong trying to export a node with more than just one user data array. Maybe I fail to see my mistake in my code.

When I add more arrays as user data to a node, then I am not able to load all the arrays again. It worked with just one array. But when I set two arrays as user data in one node, then loading the node gives me an exception (java.lang.ArrayIndexOutOfBoundsException). One array is loaded correct, the other array is null (I would say because of the exception). Both keys are exported and loaded correct.

I am using the same code from above, just a little bit modified:

public class Main extends SimpleApplication {
    
    private final static Integer[] SOME_INTEGER = {16, 17, 19, 22, 26, 29, 31, 32, 30, 27, 23, 19};
    public final static Float[] SOME_FLOAT = {153f, 156f, 97f, 51f, 17f, 3f, 0f, 0f, 7f, 46f, 130f, 186f};
    private Node node;
    
    public static void main(String[] args){
        Main app = new Main();
        app.start();
    }

    @Override
    public void simpleInitApp() {
        Box b = new Box(1, 1, 1);
        Geometry geom = new Geometry("Box", b);
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", ColorRGBA.Blue);
        geom.setMaterial(mat);
        node = new Node("Node");
        node.setUserData("MyIntegerArray", SOME_INTEGER);
        node.setUserData("MyFloatArray", SOME_FLOAT);
        rootNode.attachChild(node);
        node.attachChild(geom);
        for (String key : node.getUserDataKeys()) {
            Object[] someInteger = node.getUserData(key);
            System.out.println("KEY: " + key);
            for (Object object : someInteger) {
                System.out.println("ARRAY_VALUE: " + object);
            }
        }

//        loadFile();
    }
    
    @Override
    public void stop() {
        String userHome = System.getProperty("user.home");
        BinaryExporter exporter = BinaryExporter.getInstance();
        File file = new File(userHome+"/Models/"+"MyModel.j3o");
        try {
          exporter.save(node, file);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        super.stop();
    }
    
    private void loadFile() {
        String userHome = System.getProperty("user.home");
        assetManager.registerLocator(userHome, FileLocator.class);
        node = (Node)assetManager.loadModel("Models/MyModel.j3o");
        rootNode.attachChild(node);
        for (String key : node.getUserDataKeys()) {
            Object[] anArray = node.getUserData(key);
            System.out.println("KEY: " + key);
            for (Object object : anArray) {
                System.out.println("ARRAY_VALUE: " + object);
            }
        }
    }
    
}

After I set the user data in the node I also checked if they are set by printing all the user data. At this moment everything is correct. Just after exporting and then loading the node again, one array is null. The java.lang.ArrayIndexOutOfBoundsException comes from line

node = (Node)assetManager.loadModel("Models/MyModel.j3o");

When I print the values of the array after loading, then one array is there correct and the other array is null.

The exception looks like this (line 64 is the line from above):

Okt. 17, 2024 9:21:30 NACHM. class com.jme3.export.binary.BinaryImporter readObject(int id)
SCHWERWIEGEND: Exception
java.lang.ArrayIndexOutOfBoundsException: Index -18 out of bounds for length 4
	at com.jme3.export.binary.ByteUtils.rightAlignBytes(ByteUtils.java:483)
	at com.jme3.export.binary.BinaryInputCapsule.readInt(BinaryInputCapsule.java:834)
	at com.jme3.export.binary.BinaryInputCapsule.setContent(BinaryInputCapsule.java:154)
	at com.jme3.export.binary.BinaryImporter.readObject(BinaryImporter.java:340)
	at com.jme3.export.binary.BinaryInputCapsule.resolveIDs(BinaryInputCapsule.java:510)
	at com.jme3.export.binary.BinaryInputCapsule.readStringSavableMap(BinaryInputCapsule.java:700)
	at com.jme3.scene.Spatial.read(Spatial.java:1665)
	at com.jme3.scene.Node.read(Node.java:776)
	at com.jme3.export.binary.BinaryImporter.readObject(BinaryImporter.java:345)
	at com.jme3.export.binary.BinaryImporter.load(BinaryImporter.java:245)
	at com.jme3.export.binary.BinaryImporter.load(BinaryImporter.java:128)
	at com.jme3.export.binary.BinaryImporter.load(BinaryImporter.java:112)
	at com.jme3.export.binary.BinaryLoader.load(BinaryLoader.java:36)
	at com.jme3.asset.DesktopAssetManager.loadLocatedAsset(DesktopAssetManager.java:272)
	at com.jme3.asset.DesktopAssetManager.loadAsset(DesktopAssetManager.java:388)
	at com.jme3.asset.DesktopAssetManager.loadModel(DesktopAssetManager.java:439)
	at com.jme3.asset.DesktopAssetManager.loadModel(DesktopAssetManager.java:444)
	at com.neolithicrevolution.main.Main.loadFile(Main.java:64)
	at com.neolithicrevolution.main.Main.simpleInitApp(Main.java:45)
	at com.jme3.app.SimpleApplication.initialize(SimpleApplication.java:240)
	at com.jme3.system.lwjgl.LwjglWindow.initInThread(LwjglWindow.java:607)
	at com.jme3.system.lwjgl.LwjglWindow.run(LwjglWindow.java:710)
	at java.base/java.lang.Thread.run(Thread.java:829)