Weird ClassCastException for custom scene explorer node

I’ve been working on a plugin for the past few weeks to help me build my world in the SDK. One of the many features of this plugin is to be able to add a “physics barrier”, which is a simple quad geometry with a rigid body control attached to it.

Now to do so, I created a class “PhysicsBarrier” that extends Geometry, and I’ve also created a class “JmePhysicsBarrier” that extends JmeGeometry, so that I can have it as a custom node in the scene explorer. The classes are shown below:

PhysicsBarrier

package com.fadorico.plugins.tiledTerrainLib.spatials;

import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.gde.core.scene.SceneApplication;
import com.jme3.material.Material;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Quad;
import java.io.IOException;

public class PhysicsBarrier extends Geometry{
    
    private float width;
    private float height;
    private RigidBodyControl physicsControl;
    
    public PhysicsBarrier(){
        this(5, 5);
    }
    
    public PhysicsBarrier(float width, float height){
        super("Physics barrier", new Quad(width, height));
        this.width = width;
        this.height = height;
        physicsControl = new RigidBodyControl(0);
        addControl(physicsControl);
        setCullHint(CullHint.Always);
        //Material mat = new Material(SceneApplication.getApplication().getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
        //setMaterial(mat);
    }
    
    public float getWidth(){
        return width;
    }
    
    public float getHeight(){
        return height;
    }
    
    public void setWidth(float width){
        this.width = width;
        ((Quad)getMesh()).updateGeometry(width, height);
        physicsControl.setEnabled(false);
        physicsControl.setEnabled(true);
    }
    
    public void setHeight(float height){
        this.height = height;
        ((Quad)getMesh()).updateGeometry(width, height);
        physicsControl.setEnabled(false);
        physicsControl.setEnabled(true);
    }
    
    @Override
    public void write(JmeExporter ex) throws IOException {
        super.write(ex);
        OutputCapsule capsule = ex.getCapsule(this);
        capsule.write(width, "Width", 5);
        capsule.write(height, "Height", 5);
    }
    
    @Override
    public void read(JmeImporter im) throws IOException {
        super.read(im);
        InputCapsule capsule = im.getCapsule(this);
        width = capsule.readFloat("Width", 5);
        height = capsule.readFloat("Height", 5);
    }
}

JmePhysicsBarrier

package com.fadorico.plugins.tiledTerrainLib.spatials;

import com.jme3.gde.core.sceneexplorer.nodes.JmeGeometry;
import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatial;
import com.jme3.gde.core.sceneexplorer.nodes.JmeSpatialChildren;
import com.jme3.gde.core.sceneexplorer.nodes.SceneExplorerNode;
import com.jme3.scene.Geometry;
import java.awt.Image;
import java.io.File;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openide.loaders.DataObject;
import org.openide.nodes.Sheet;
import org.openide.util.ImageUtilities;

@org.openide.util.lookup.ServiceProvider(service=SceneExplorerNode.class)
public class JmePhysicsBarrier extends JmeSpatial{
    
    private static Image smallImage = ImageUtilities.loadImage("com/fadorico/plugins/tiledTerrainLib/icons/barrier.gif");
    private PhysicsBarrier physicsBarrier;
    
    public JmePhysicsBarrier(){
    }
    
    public JmePhysicsBarrier(PhysicsBarrier spatial, JmeSpatialChildren children){
        super(spatial, children);
        getLookupContents().add(spatial);
        physicsBarrier = spatial;
        setName(spatial.getName());
    }
    
    @Override
    public Image getIcon(int type) {
        return smallImage;
    }
 
    @Override
    public Image getOpenedIcon(int type) {
        return smallImage;
    }
    
    @Override
    protected Sheet createSheet() {
        Sheet sheet = super.createSheet();
        Sheet.Set set = Sheet.createPropertiesSet();
        set.setDisplayName("Physics barrier");
        set.setName(PhysicsBarrier.class.getName());
        
        if (physicsBarrier == null) {
            return sheet;
        }
        
        set.put(makeProperty(physicsBarrier, float.class, "getWidth", "setWidth", "Width"));
        set.put(makeProperty(physicsBarrier, float.class, "getHeight", "setHeight", "Height"));
 
        sheet.put(set);
        return sheet;
 
    }
 
    @Override
    public Class getExplorerNodeClass(){
        return JmePhysicsBarrier.class;
    }

    @Override
    public Class getExplorerObjectClass(){
        return PhysicsBarrier.class;
    }
    
    @Override
    public org.openide.nodes.Node[] createNodes(Object key, DataObject key2, boolean readOnly) {
        JmeSpatialChildren children=new JmeSpatialChildren((com.jme3.scene.Spatial)key);
        children.setReadOnly(readOnly);
        return new org.openide.nodes.Node[]{new JmePhysicsBarrier((PhysicsBarrier) key, children).setReadOnly(readOnly)};
    }
}

I can successfully add my physics barrier into the scene explorer. However, when I save & close and then try to reopen my scene, I get this weird exception:

    java.lang.ClassCastException: com.fadorico.plugins.tiledTerrainLib.spatials.PhysicsBarrier cannot be cast to com.fadorico.plugins.tiledTerrainLib.spatials.PhysicsBarrier
    	at com.fadorico.plugins.tiledTerrainLib.spatials.JmePhysicsBarrier.createNodes(JmePhysicsBarrier.java:75)
    	at com.jme3.gde.core.sceneexplorer.nodes.JmeSpatialChildren.createNodes(JmeSpatialChildren.java:138)
    	at org.openide.nodes.Children$Keys$KE.nodes(Children.java:1662)

............

Does anyone know why I would be getting this??? I mean clearly I’m trying to cast the right object but for some reason it doesn’t recognize it.

I guess its also worth mentioning that I’ve been following this tutorial here http://wiki.jmonkeyengine.org/doku.php/sdk:development:sceneexplorer and that I use the last available nightly build.

mhh every time I’ve seen this kiind of class clast exception was that the version of the classes did not match, and because the class was coming from a lib that was twice in the classpath with different versions.
I don’t know how it can be applicable to your plugin though. Where does that class come from?

This should only happen for the same class loaded from two different classloaders. Different jars with the same class is not enough to trigger this as the JVM will generally only load the first class it finds (for a given classloader).

One way to confirm/diagnose… put some System.out.printlns (or step in the debugger) around:
JmePhysicsBarrier.createNodes(JmePhysicsBarrier.java:75)

Log things like:
PhysicsBarrier.class == key.getClass()
and:
PhysicsBarrier.class
key.getClass()
PhysicsBarrier.class.getClassLoader()
key.getClass().getClassLoader()

It won’t directly lead to a solution but it will at least show you what’s going on.
(The first will most certainly be false.)

The class is located in my plugin and I use a NewSpatialAction to add a new instance of the class

@Override
    protected Spatial doCreateSpatial(Node node){
        return new PhysicsBarrier(5, 5);
}

After banging my head on my computer for the past few hours I’ve found the source of the problem, but no solution as of yet. As both of you pointed out, there are indeed two different versions of the same class that are being used. It turns out that when the scene is saved and calls the write() method of the PhysicsBarrier class, the plugin’s class loader is used (string value of getClass().getClassLoader() is ModuleCL@43f2787b[com.fadorico.plugins.tiledTerrainLib]). But when I reopen the scene, the read() method is called and at that point the class loader that is being used is (i think) the default JVM class loader (string value of getClass().getClassLoader() java.net.URLClassLoader@7348ac7a).

So I’m not sure what the best solution would be. The only solution I currently see is to somehow force my NewSpatialAction class to create a new instance of PhysicsBarrier using the default JVM loader, thus (from my understanding), would force the read and the write method to be called from the same class loader.

EDIT: I don’t 100% understand this side of Java, so I am opened to explanations/solutions.

Took me a while to figure out a solution, but I found some sort of workaround. It turns out that the URLClassLoader that was being called when opening back up the scene was a class loader belonging to the scene’s asset manager. So to fix this, I just added my plugin’s class loader to the asset manager and deleted the other one. Not to sure what the repercussions of deleting the other class loader are, but currently I don’t think I need it. In the future I’m pretty sure I could just have both class loaders in the asset manager at the same time with a little tweaking. Either way, thank you both for pointing me in the right direction!

1 Like

Hm… The ProjectAssetManager in the SDK has access to the class loader of the project (with all compiled files and added libraries) and all the assets in the projects assets folder. I guess the problem is rather that you use your own AssetManager instead of using the ProjectAssetManager. (All in case I get it right that you’re actually creating a SDK plugin).

If you’re actually using the ProjectAssetManager already then deleting the classloader from that is certainly going to create issues down the road.

Ya I kind of figured as much :stuck_out_tongue: . But the problem, I figured, is actually that I create my spatials dynamically through the plugin. The NewSpatialAction class is created through the class loader of the plugin, thus when I create a new instance of the physics barrier, its created through the same class loader as the NewSpatialAction, being the plugin’s class loader. When I close and save the file, the barrier is saved using the write(), from that same class loader. But when I reopen the file in the SDK, the SceneApplication’s asset manager opens the file, using its own class loader. But since the barrier was saved with an instance of the plugin’s class loader, I get the exception.

So I believe pretty much the true way of fixing it would be to have a generic j3o file saved in the asset folder that contains the base of my barrier. When I call NewSpatialAction, I would load that j3o using the asset manager, which would in theory fix the problem, since now it would save the barrier as an instance of the asset manager’s class loader.

Sorry if that got confusing :stuck_out_tongue: