Java: use clipboard to copy-paste java objects bet

2019-02-13 17:45发布

问题:

I am trying to implement copy-paste of objects between different instances of same application. Currently it works only in one application (I mean, copy and paste in the same instance of application), but does not work between different instances.

Copying code:

// MyObject is a class of objects I want to copy/paste;
// MyObjectSelection is a class that impements Transferable and ClipboardOwner interfaces

Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
MyObject data = new MyObject(selectedItems);
MyObjectSelection dataSelection = new MyObjectSelection(data);
clipboard.setContents(dataSelection, this);

After that, I can check contents of the clipboard, like that:

Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
Transferable clipboardContent = clipboard.getContents(this);

DataFlavor[] flavors = clipboardContent.getTransferDataFlavors();
System.out.println("flavors.length=" + flavors.length);
for (int i = 0; i < flavors.length; i++){
   System.out.println("flavor=" + flavors[i]);
}

If I do this from the same application instance that I copied object, it works: flavors.length is 1, mimetype is application/x-java-serialized-object, and, well, it works.

But if I open application, perform copy, then open the same application again (first one isn't closed, i.e. both instances are running simultaneously), and try to check clipboard contents from there, then flavors.length is 0.

I examined the docs and these examples: one, two, but I still can't find what is wrong in my implementation.

Did I miss something?


UPD: I added sscce: clipboard_test.zip.

This is test application (I use Eclipse to build it) with 3 source files:

  • ClipboardTest.java - main app class
  • MyObject.java - class for objects to copy/paste (this class contains just array of String)
  • MyObjectSelection.java - class that implements Transerable and ClipboardOwner interfaces

    There are two buttons: "copy", "test".

    When you press "copy" button, new instance of MyObject is created and put to the clipboard.

    When you press "test" button, clipboard contents are checked and echoed to the console (count of supported DataFlavor's, and each DataFlavor)

    So, replicate these steps:

  • Start application

  • Press "copy" button: you will see "object copied" message in the log
  • Press "test" button: you will see clipboard's contents:

       flavors.length = 1
       flavor[0] = java.awt.datatransfer.DataFlavor[mimetype=application/x-java-serialized-object;representationclass=MyObject]
    
  • Start another instance of the application (don't close the first one)

  • Press "test" button: you will see that clipboard is empty:

       flavors.length = 0
    

Why is that?


UPD2: sscce posted directly here:

  import java.awt.BorderLayout;

  import java.awt.datatransfer.Clipboard;
  import java.awt.datatransfer.ClipboardOwner;
  import java.awt.datatransfer.DataFlavor;
  import java.awt.datatransfer.Transferable;
  import java.awt.datatransfer.UnsupportedFlavorException;

  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;

  import java.awt.Toolkit;

  import javax.swing.JButton;
  import javax.swing.SwingUtilities; 
  import javax.swing.JFrame;

  public final class ClipboardTest implements Runnable, ClipboardOwner {

     public static void main(String[] args) {
        SwingUtilities.invokeLater (new ClipboardTest());
     } 

     public void run() {

        JFrame f = new JFrame ("Clipboard test");
        f.setDefaultCloseOperation (JFrame.DISPOSE_ON_CLOSE);

        //-- Create "copy" button.
        //   When you click it, new object "test_items" becomes created
        //   and put to the clipboard.
        {
           JButton button_copy = new JButton("copy");
           button_copy.addActionListener(new ActionListener(){
              public void actionPerformed(ActionEvent e){

                 String[] test_items = {
                    "one", "two", "three"
                 };

                 Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
                 MyObject data = new MyObject(test_items);
                 MyObjectSelection dataSelection = new MyObjectSelection(data);
                 clipboard.setContents(dataSelection, ClipboardTest.this);

                 System.out.println("object copied");
              }
           });
           f.add(button_copy, BorderLayout.NORTH);
        }

        //-- Create "test" button.
        //   When you click it, clipboard contents are checked
        //   and echoed to the console (count of supported DataFlavor's, and each DataFlavor)
        {
           JButton button_test = new JButton("test");
           button_test.addActionListener(new ActionListener(){
              public void actionPerformed(ActionEvent e){

                 Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
                 Transferable clipboardContent = clipboard.getContents(this);

                 DataFlavor[] flavors = clipboardContent.getTransferDataFlavors();
                 System.out.println("flavors.length = " + flavors.length);
                 for (int i = 0; i < flavors.length; i++){
                    System.out.println("flavor[" + i + "] = " + flavors[i]);
                 }

              }
           });
           f.add(button_test, BorderLayout.SOUTH);
        }

        f.pack();
        f.setVisible(true);
     }



     // ClipboardOwner implementation

     @Override
     public void lostOwnership(Clipboard clipboard, Transferable transferable){
        System.out.println("ClipboardTest: Lost ownership");
     }







     /* *****************************************************************************************
      * Object that I want to copy/paste
      * ****************************************************************************************/

     public static class MyObject {

        private String[] items;

        public MyObject(String[] items){
           this.setItems(items);
        }

        public String[] getItems(){
           return this.items;
        }

        public void setItems(String[] items){
           this.items = items;
        }

     }




     public static class MyObjectSelection implements Transferable, ClipboardOwner {

        private static DataFlavor dmselFlavor = new DataFlavor(MyObject.class, "Test data flavor");
        private MyObject selection;



        public MyObjectSelection(MyObject selection){
           this.selection = selection;
        }


        // Transferable implementation

        @Override
        public DataFlavor[] getTransferDataFlavors(){
           System.out.println("getTransferDataFlavors");
           DataFlavor[] ret = {dmselFlavor};
           return ret;
        }

        @Override
        public boolean isDataFlavorSupported(DataFlavor flavor){
           return dmselFlavor.equals(flavor);
        }

        @Override
        public synchronized Object getTransferData (DataFlavor flavor)
           throws UnsupportedFlavorException 
        {
           if (isDataFlavorSupported(flavor)){
              return this.selection;
           } else {
              throw new UnsupportedFlavorException(dmselFlavor);
           }
        }



        // ClipboardOwner implementation

        @Override
        public void lostOwnership(Clipboard clipboard, Transferable transferable){
           System.out.println("MyObjectSelection: Lost ownership");
        }

     }

  }

回答1:

Quoting this tutorial:

Transferring the data using this mechanism uses Object serialization, so the class you use to transfer the data must implement the Serializable interface, as must anything that is serialized with it. If everything is not serializable, you will see a NotSerializableException during drop or copy to the clipboard.

Your MyObject is not Serializable, so it cannot work. The framework apparently detects this fact (as opposed to a situation with an unserializable subclass of a serializable parent class, or similar scenarios where non-serializability is detected only in the process), so it won't even offer that flavor to other processes.

In general, two instances of the same java application won't have the same address space, so they can't simply access one another's memory. Therefore everything you transfer has to be serialized.