javaswingdrag-and-dropjtreenotserializableexception

JTree D&D DataFlavor issue (NotSerializableException in some cases)


I'm implementing D&D for a JTree. I have written a custom TransferHandler and created a new Transferable class. This class is very easy:

public class TreePathTransferable implements Transferable{

  private static final DataFlavor[] flavors = new DataFlavor[] {
          new DataFlavor(TreePath[].class,"TreePaths")};

  private TreePath[] data;

  public TreePathTransferable(TreePath[] data) {
    super();
    this.data = data;
  }

  public DataFlavor[] getTransferDataFlavors() {
    return flavors;
  }

  public boolean isDataFlavorSupported(DataFlavor flavor) {
    return flavors[0].equals(flavor);
  }

  public TreePath[] getTransferData(DataFlavor flavor) throws
          UnsupportedFlavorException, IOException{
    return data;
  }
}

If "on drop" I call

dtde.getTransferable().getTransferData(dtde.getTransferable().getTransferDataFlavors()[0])

I get a java.io.NotSerializableException. If I change class of the data-object in TreePathTransferable to Object in the following way:

public class TreePathTransferable implements Transferable{

  private static final DataFlavor[] flavors = new DataFlavor[] {
          new DataFlavor(Object.class,"Object")};

  private Object data;

  public TreePathTransferable(Object data) {
    super();
    this.data = data;
  }

  public DataFlavor[] getTransferDataFlavors() {
    return flavors;
  }

  public boolean isDataFlavorSupported(DataFlavor flavor) {
    return flavors[0].equals(flavor);
  }

  public Object getTransferData(DataFlavor flavor) throws
          UnsupportedFlavorException, IOException{
    return data;
  }

}

Everything works fine. This makes no sense for me since transferred data are same - TreePath[]. Why the difference?

Stack trace

Note: I have replaced the non-serializable class with XXX. This is a property of a subclass of DefaultMutableTreeNode. This subclass is not serializable, so the Exception is meaningful, but why she doesn't appear in the second case?

java.io.NotSerializableException: XXX
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at java.util.HashMap.internalWriteEntries(HashMap.java:1777)
    at java.util.HashMap.writeObject(HashMap.java:1354)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:988)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.writeArray(ObjectOutputStream.java:1378)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1174)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at javax.swing.tree.DefaultMutableTreeNode.writeObject(DefaultMutableTreeNode.java:1287)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:988)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.writeArray(ObjectOutputStream.java:1378)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1174)
    at java.io.ObjectOutputStream.access$300(ObjectOutputStream.java:162)
    at java.io.ObjectOutputStream$PutFieldImpl.writeFields(ObjectOutputStream.java:1707)
    at java.io.ObjectOutputStream.writeFields(ObjectOutputStream.java:482)
    at java.util.Vector.writeObject(Vector.java:1077)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:988)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
    at java.io.ObjectOutputStream.defaultWriteObject(ObjectOutputStream.java:441)
    at javax.swing.tree.DefaultMutableTreeNode.writeObject(DefaultMutableTreeNode.java:1278)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:988)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
    at java.io.ObjectOutputStream.defaultWriteObject(ObjectOutputStream.java:441)
    at javax.swing.tree.DefaultMutableTreeNode.writeObject(DefaultMutableTreeNode.java:1278)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:988)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
    at java.io.ObjectOutputStream.defaultWriteObject(ObjectOutputStream.java:441)
    at javax.swing.tree.DefaultMutableTreeNode.writeObject(DefaultMutableTreeNode.java:1278)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:988)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
    at java.io.ObjectOutputStream.defaultWriteObject(ObjectOutputStream.java:441)
    at javax.swing.tree.DefaultMutableTreeNode.writeObject(DefaultMutableTreeNode.java:1278)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:988)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.writeArray(ObjectOutputStream.java:1378)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1174)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at sun.awt.datatransfer.TransferableProxy.getTransferData(TransferableProxy.java:83)
    at java.awt.dnd.DropTargetContext$TransferableProxy.getTransferData(DropTargetContext.java:376)

TransferHandler

public class MyTreeTransferHandler extends TransferHandler {
  public MyTreeTransferHandler() {
    super();
  }

  protected Transferable createTransferable(JComponent c) {
    Transferable t = null;
    if(c instanceof JTree) {
      JTree tree = (JTree) c;
        t = new TreePathTransferable(tree.getSelectionPaths());
        return t;
    }
    return t;
  }

  public int getSourceActions(JComponent c) {
    return TransferHandler.MOVE;
  }

  public boolean canImport(JComponent c, DataFlavor[] flavors){
    return true;
  }
}

Solution

  • Your situation is caused by three things:

    1. You used DataFlavor constructor where you do not directly specify to not serialize transferred data. It is kind of indirect anyway (see DataFlavor.javaJVMLocalObjectMimeType or this question)
    2. D&D mechanism tries to serialize transferred data if it can. Reason is: "This insulates applications sharing DnD and Clipboard data from each other".
    3. D&D mechanism checks if it can serialize data by checking class you provided in DataFlavor constructor, not runtime class of object transferred.

    Following your stacktrace, this can be seen in TransferableProxy source code:

    public Object getTransferData(DataFlavor df)
            throws UnsupportedFlavorException, IOException
        {
            Object data = transferable.getTransferData(df);
            // If the data is a Serializable object, then create a new instance
            // before returning it. This insulates applications sharing DnD and
            // Clipboard data from each other.
            if (data != null && isLocal && df.isFlavorSerializedObjectType()) {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                ClassLoaderObjectOutputStream oos =
                    new ClassLoaderObjectOutputStream(baos);
                oos.writeObject(data);
                ByteArrayInputStream bais =
                    new ByteArrayInputStream(baos.toByteArray());
                try {
                    ClassLoaderObjectInputStream ois =
                        new ClassLoaderObjectInputStream(bais,
                                                         oos.getClassLoaderMap());
                    data = ois.readObject();
                } catch (ClassNotFoundException cnfe) {
    
                    throw (IOException)new IOException().initCause(cnfe);
                }
            }
            return data;
    }
    

    Important part is df.isFlavorSerializedObjectType(). This calls isRepresentationClassSerializable method implemented as return java.io.Serializable.class.isAssignableFrom(representationClass);.

    So this compares Class you provided in DataFlavor constructor to Serializable interface. Since TreePath[].class implements Serializable and Object does not, serialization is not performed for Object class (Using new DataFlavor(Object.class,"Object") constructor call).