Java Technology Home Page
A-Z Index

Java Developer Connection(SM)
Books

Downloads, APIs, Documentation
Java Developer Connection
Tutorials, Tech Articles, Training
Online Support
Community Discussion
News & Events from Everywhere
Products from Everywhere
How Java Technology is Used Worldwide
Print Button
 
Book Excerpt Index

Mastering JavaTM 2
Chapter 16: Transferring Data

(Jan 1999)

This is an excerpt from Mastering JavaTM 2, published by Sybex Inc. Written by John Zukowski, a long-time contributor to the JDC, this book offers in-depth coverage of Java 2, including such topics as building forms with Swing, creating Java graphics with the 2D API, implementing drag-and-drop support, providing built-in security with the Java Cryptography architecture, integrating your Java program with CORBA, enhancing server-side performance with servlets, and building fast Java solutions. The book comes with a CD that contains all the example code and executable files, along with links to third-party tools to use with the Java 2 platform.

This chapter explores the data transfer capabilities of the Java 2 platform, which includes access to the system clipboard as well as how to use the drag and drop features.

Note: For an updated drag and drop example that includes using a custom data flavor as well as dropping a file list, see Drag and Drop, Take 3 from the Mining Co.

The java.awt.datatransfer and java.awt.dnd packages encompass two very different, yet similar capabilities. These data transfer capabilities available within Java are to transfer data to a clipboard as well as to drag and drop within an application or between applications. The means that both packages used to transfer data is the same, so it will be explained first.

Transferable Objects

Objects that are transferable to/from an application's clipboard or for drag and drop must implement the Transferable interface, shown here.

  public interface Transferable {
    public abstract Object 
      getTransferData(DataFlavor flavor) 
      throws UnsupportedFlavorException, IOException;
    public abstract DataFlavor[] 
      getTransferDataFlavors();
    public abstract boolean isDataFlavorSupported(
      DataFlavor flavor);
  }

NOTE:
A clipboard is an area within memory for storing temporary information in support of cut, copy, and paste tasks. There tends to be two types of clipboards: a "System" clipboard to share data between applications and private clipboards for sharing data within a single application.

When looking at the interface definition, the first thing that you may ask is "What's a DataFlavor?" A flavor is a way to represent data when it is being transferred. This allows the receiving class to ask for a flavor it understands. As the following class definition shows, there is much that needs to be done to describe how the data is represented.

  public class DataFlavor implements Externalizable, 
   Cloneable {
    public static final DataFlavor javaFileListFlavor;
    public static final String 
      javaJVMLocalObjectMimeType;
    public static final String javaRemoteObjectMimeType;
    public static String javaSerializedObjectMimeType;
    public static final DataFlavor plainTextFlavor;
    public static final DataFlavor stringFlavor;
    public DataFlavor();
    public DataFlavor(
      Class representationClass, 
      String humanPresentableName);
    public DataFlavor(String mimeType) 
      throws MimeTypeParseException,
      ClassNotFoundException;
    public DataFlavor(
      String mimeType,
      String humanPresentableName);
    public DataFlavor(
      String primaryType, 
      String subType,
      MimeTypeParameterList params, 
      Class representationClass, 
      String humanPresentableName);
    public Object clone() throws 
      CloneNotSupportedException;
    public boolean equals(DataFlavor dataFlavor);
    public boolean equals(Object obj);
    public boolean equals(String s);
    public boolean equals(MimeType mt);
    public String getHumanPresentableName();
    public String getMimeType();
    public String getParameter(String paramName);
    public String getPrimaryType();
    public Class getRepresentationClass();
    public String getSubType();
    public boolean isFlavorJavaFileListType();
    public boolean isFlavorRemoteObjectType();
    public boolean isFlavorSerializedObjectType();
    public final boolean isMimeTypeEqual(
      DataFlavor dataFlavor);
    public boolean isMimeTypeEqual(String mimeType);
    public boolean isMimeTypeEqual(MimeType mimeType);
    public boolean isMimeTypeSerializedObject();
    public boolean isRepresentationClassInputStream();
    public boolean isRepresentationClassRemote();
    public boolean isRepresentationClassSerializable();
    protected String normalizeMimeType(String mimeType);
    protected String normalizeMimeTypeParameter(
      String name, String value);
    public synchronized 
      void readExternal(ObjectInput oi) 
      throws IOException,
      ClassNotFoundException;
    public void setHumanPresentableName(
      String humanPresentableName);
    public synchronized 
      void writeExternal(ObjectOutput oo)
      throws IOException;
  }

Thankfully, the class defines several constants for frequently used flavors: javaFileListFlavor, javaJVMLocalObjectMimeType, javaRemoteObjectMimeType, javaSerializedObjectMimeType, plainTextFlavor, and stringFlavor. When you decide you want some information to be Transferable, you decide in which flavors you are going to make the data available. These flavors are dealt with internally as the familiar Internet standard of MIME types, and there is a MimeType class to help. However, it is rarely necessary to think in terms of MIME types. It is just the naming scheme Java reuses to identify flavors. You may display the DataFlavor as the presentable name with getHumanPresentableName(), but that would be the only time you would see something like application/x-java-serialized-object, which happens to be the presentable name for javaSerializedObjectMimeType.


NOTE:
If you are not familiar with MIME types, they are defined by RFC 2045 and 2046. MIME stands for Multipurpose Internet Mail Extension.
This is the way flavors work. When you have some information to transfer, you identify the formats so that the target object can receive the data. For instance, if the source of a transfer is the contents of a JTextPane, which supports multi-attributed text, you might support three flavors:

  • A flavor based on javaSerializedObjectMimeType to preserve the attributes, so it can be pasted into another JTextPane

  • A stringFlavor, so anyone who understands UNICODE strings can receive it, but will lose formatting information

  • A plainTextFlavor, where each character is represented by only 8 bits, losing data from any non-ASCII characters

This allows the target of the data transfer to ask the Transferable object if it supports a DataFlavor it understands, via either getTransferDataFlavors() to get a list of all flavors or isDataFlavorSupported(DataFlavor) to inquire about a specific one. The target would start with the richest form it understands, hoping to retain as much information as possible. If the target object was a JTextArea, which doesn't support multi-attributed text, the best flavor it could ask for is stringFlavor.

As the concept of transferring Java String objects is fairly common, Java provides the StringSelection support class to help. It provides a Transferable object that can provide the target with its data as one of two flavors: stringFlavor or plainTextFlavor.

  public class StringSelection implements Transferable, 
   ClipboardOwner {
    public StringSelection(String string);
    public synchronized Object getTransferData(
     DataFlavor flavor)
      throws UnsupportedFlavorException, IOException;
    public synchronized DataFlavor[] 
     getTransferDataFlavors();
    public boolean isDataFlavorSupported(
     DataFlavor flavor);
    public void lostOwnership
     (Clipboard clip, Transferable t);
  }

Clipboard

As previously stated, the clipboard is an area within memory for storing temporary information in support of cut, copy, and paste tasks. Asking the Toolkit object, with getSystemClipboard(), provides access to the system clipboard. You can also create private clipboards, inaccessible beyond the scope of the program. The Clipboard class is rather simple and defines the scope of its capabilities:
  public class Clipboard {
    public Clipboard(String name);
    public synchronized Transferable 
     getContents(Object obj);
    public String getName();
    public synchronized void 
     setContents(Transferable t, ClipboardOwner own);
  }

WARNING:
By default, access to the system clipboard is protected by the security manager and not permitted from applets.

Basically, you can name the clipboard (from the constructor) as well as set or get its contents. When you place something on the clipboard, it actually isn't copied until someone tries to get it, so the source must be sure the object is available while it remains on the clipboard. To find out when an object placed on the clipboard has been replaced is done with the help of the ClipboardOwner interface.

  public interface ClipboardOwner {
    public abstract void lostOwnership(
     Clipboard clip, Transferable t);
  }

The previously mentioned StringSelection class handles this for you if you are transferring String objects. It just retains a reference to the String to transfer. For other types of transferable objects, you would have to do the same. If the originally copied data were no longer available, it would be necessary to throw an IOException.

As the "Editor Kits and Text Actions" section of Chapter 15 showed, when working with the Swing text components, the cut, copy, and paste operations are automatically handled for you with the help of the installed EditorKit. So, you normally do not have to worry about creating code to access the clipboard. However, to demonstrate the capabilities, we'll create a program that manually does copy and paste with a JTextArea. Figure 16.1 shows the sample program screen.

Clipboard accessing program
Figure 16.1: Clipboard accessing program

The program behind the application shown in Figure 16.1 follows. Be sure to try pasting from external applications, not just from what was copied with the program.

  import java.awt.*;
  import java.awt.event.*;
  import com.sun.java.swing.*;
  import java.awt.datatransfer.*;
  import java.io.IOException;
  public class ClipIt {
    public static void main (String args[]) {
    final Clipboard clipboard =
      Toolkit.getDefaultToolkit().getSystemClipboard();
    JFrame f = new JFrame ("Clip It");
    f.addWindowListener (new WindowAdapter() {
      public void windowClosing (WindowEvent e) {
        System.exit (0);
      }
    });
    Container c = f.getContentPane();
    final JTextArea jt = new JTextArea();
    JScrollPane pane = new JScrollPane (jt);
    c.add (pane, BorderLayout.CENTER); 
    JButton copy = new JButton ("Copy");
    copy.addActionListener (new ActionListener() {
      public void actionPerformed (ActionEvent e) {
        String selection = jt.getSelectedText();
        StringSelection data = new StringSelection(
         selection);
        clipboard.setContents (data, data);
      }
    });
    JButton paste = new JButton ("Paste");
    paste.addActionListener (new ActionListener() {
      public void actionPerformed (ActionEvent e) {
        Transferable clipData = clipboard.getContents
         (clipboard);
        if (clipData != null) {
          try {
            String s = (String)(clipData.getTransferData
             (
              DataFlavor.stringFlavor));
            jt.replaceSelection (s);
          } catch (UnsupportedFlavorException ee) {
            System.err.println ("Unsupported flavor: 
             " + ee);
          } catch (IOException ee) {
            System.err.println ("Unable to get data: 
             " + ee);
          }
        }
      }
    });
    JPanel p = new JPanel();
    p.add (copy);
    p.add (paste);
    c.add (p, BorderLayout.SOUTH);
    f.setSize (300, 300);
    f.setVisible (true);
  }
}

NOTE:
Java does not currently support pasting objects like images from external applications. You could copy an ImageIcon and treat it as a javaSerializedObjectMimeType. However, it would then only be pasteable into another Java program, or a program that understands Java's serialization mechanism, which will be described in the "Object Persistence and Serialization" section of Chapter 19.

Drag and Drop

Drag and drop within Java is similar to accessing the clipboard, except the process of moving the data is a little more complicated. The data that you wish to transfer still needs to implement the Transferable interface. Besides the data to transfer, there is a source and a destination. The source of the dragging implements the DragSourceListener interface:
public interface DragSourceListener 
    extends EventListener{
  public abstract void dragDropEnd(
     DragSourceDropEvent e);
  public abstract void dragEnter(
     DragSourceDragEvent e);
  public abstract void dragExit(
     DragSourceEvent e);
  public abstract void dragOver(
     DragSourceDragEvent e);
  public abstract void dropActionChanged(
     DragSourceDragEvent e);
}

While the target implements DropTargetListener:

  public interface DropTargetListener 
      extends EventListener{
    public abstract void 
     dragEnter(DropTargetDragEvent e);
    public abstract void 
     dragExit(DropTargetEvent e);
    public abstract void 
     dragOver(DropTargetDragEvent e);
    public abstract void 
     drop(DropTargetDropEvent e);
    public abstract void 
     dropActionChanged(DropTargetDragEvent e);
}

One thing should be pointed out. There are three different event types for each set of five methods: DragSourceEvent, DragSourceDragEvent, and DragSourceDropEvent for dragging; and DropTargetEvent, DropTargetDropEvent, and DropTargetDragEvent for dropping. This requires a little extra care when implementing each interface.

The source of a dragging is a DragSource. The draggable object would create an instance of DragSource within its constructor.

  public class DragSource extends Object{
    public static final Cursor DefaultCopyDrop;
    public static final Cursor DefaultCopyNoDrop;
    public static final Cursor DefaultLinkDrop;
    public static final Cursor DefaultLinkNoDrop;
    public static final Cursor DefaultMoveDrop;
    public static final Cursor DefaultMoveNoDrop;
    public DragSource();
    public static DragSource getDefaultDragSource();
    public static boolean isDragImageSupported();
    public DragGestureRecognizer 
      createDefaultDragGestureRecognizer(
      Component target,
      int actions,
      DragGestureListener l);
    public DragGestureRecognizer 
      createDragGestureRecognizer(
      Class recognizerAbstractClass, 
      Component target, 
      int actions,
      DragGestureListener l);
    protected DragSourceContext createDragSourceContext(
      DragSourceContextPeerdscp, 
      DragGestureEvent dgl,
      Cursor dragCursor,  
      Image dragImage,  
      Point imageOffset,  
     Transferable t,
     DragSourceListener l);
   public FlavorMap getFlavorMap();
   public void startDrag(
     DragGestureEvent trigger, 
     Cursor dragCursor, 
     Image
     dragImage, 
     Point dragOffset,
     Transferable t, 
     DragSourceListener l)  
     throws InvalidDnDOperationException
     public void startDrag(
     DragGestureEvent trigger, 
     Cursor dragCursor, 
     Image
     dragImage, 
     Point imageOffset,
     Transferable t, 
     DragSourceListener l, 
     FlavorMap map)  
     throws
     InvalidDnDOperationException;
   public void startDrag(
     DragGestureEvent trigger, 
     Cursor dragCursor,
     Transferable t, 
     DragSourceListener l)
     throws InvalidDnDOperationException;
   public void startDrag(
     DragGestureEvent trigger, 
     Cursor dragCursor,
     Transferable t, 
     DragSourceListener l,  
     FlavorMap map)
     throws InvalidDnDOperationException;

The other thing you must do in the constructor is create a DragGestureRecognizer. Since detecting when the dragging has started is platform specific, this is available from the Toolkit via createDragGestureRecognizer(). Instead of asking the Toolkit for the recognizer, there is a convenience method createDefaultDragGestureRecognizer() available from DragSource that both creates the recognizer and registers a DragGestureListener.

public interface DragGestureListener 
 extends EventListener {
    public abstract void dragGestureRecognized(
     DragGestureEvent e);
}

It is within this dragGestureRecognized() method that you notify the DragSource the dragging has started and call its startDrag() method.

To put all this together, create a draggable JLabel object. This involves subclassing JLabel and implementing DragSourceListener and DragGestureListener. The actual DragSourceListener methods do nothing, as all the work will be in the dragGestureRecognized() method.

  import java.awt.*;
  import java.awt.dnd.*;
  import java.awt.datatransfer.*;
  import com.sun.java.swing.*;
  import com.sun.java.swing.event.*;

  public class DraggableLabel extends JLabel implements 
   DragSourceListener,

    DragGestureListener {

    DragSource dragSource;

    public DraggableLabel(String s) {
    super (s);
    dragSource = new DragSource();
    dragSource.createDefaultDragGestureRecognizer(
      this, DnDConstants.ACTION_COPY, this);
    }

    public void dragGestureRecognized(
     DragGestureEvent e) {
     StringSelection text = 
      new StringSelection(getText());
     dragSource.startDrag(
      e, 
      DragSource.DefaultCopyDrop, 
      text, 
      this);
    }

   public void dragDropEnd (DragSourceDropEvent e){}

    public void dragEnter (DragSourceDragEvent e){}
    public void dragExit (DragSourceEvent e){}
    public void dragOver (DragSourceDragEvent e){}

    public void dropActionChanged 
      (DragSourceDragEvent e){}
  }

The only part not explained yet is the DnDConstants. The different actions of the events are defined within the DnDConstants class.

  public final class DnDConstants {
    public static final int ACTION_COPY;
    public static final int ACTION_COPY_OR_MOVE;
    public static final int ACTION_LINK;
    public static final int ACTION_MOVE;
    public static final int ACTION_NONE;
    public static final int ACTION_REFERENCE;
    public DnDConstants();
  }

NOTE:
In JDK 1.2beta4, this is an abstract class. However, it should be changed to an interface by the time 1.2 is released.

The component you'll drop the draggable label into is a JList. This involves subclassing and implementing the DropTargetListener. There are two methods of the interface that we need to implement: dragEnter() and drop(). In dragEnter() you need to tell the component what type of drag events you accept. Then in drop() you accept the drop, process the dragged data, and tell the event you're done with it. As with clipboard operations, the dragged data is dealt with via flavors.

  import java.awt.*;
  import java.awt.dnd.*;
  import com.sun.java.swing.*;
  import java.awt.datatransfer.*;
  import java.io.IOException;
  import java.util.Vector;

  public class DroppableList extends JList 
   implements DropTargetListener {
  
    DropTarget dropTarget;
    Vector v = new Vector();
  
      public DroppableList() {
      dropTarget = new DropTarget (this, this);
    }
  
      public void dragEnter (DropTargetDragEvent e){
      e.acceptDrag (DnDConstants.ACTION_COPY);
    }
  
      public void dragExit (DropTargetEvent e){
    }
  
      public void dragOver (DropTargetDragEvent e){
    }
  
  
      public void drop (DropTargetDropEvent e){
        try {
          Transferable tr = e.getTransferable();
          if (tr.isDataFlavorSupported (
              DataFlavor.stringFlavor)) {
            e.acceptDrop 
             (DnDConstants.ACTION_COPY_OR_MOVE);
            String s = (String)tr.getTransferData (
              DataFlavor.stringFlavor);
            v.add (s);
            setListData (v);
            paintImmediately(getVisibleRect());
            e.getDropTargetContext().dropComplete(true);
          } else {
            System.err.println ("Rejected");
            e.rejectDrop();
          }
        } catch (IOException io) {
          io.printStackTrace();
          e.rejectDrop();
        } catch (UnsupportedFlavorException ufe){
          ufe.printStackTrace();
          e.rejectDrop();
        }
      }
  
      public void dropActionChanged 
       (DropTargetDragEvent e){
      }
    }

To demonstrate the droppable list and draggable label, Figure 16.2 shows a program with four labels around the list after several drag operations.

Drag and Drop demo program
Figure 16.2: Drag and Drop demonstration program

The test program behind Figure 16.2 is below:

  import java.awt.*;
  import java.awt.event.*;
  import com.sun.java.swing.*;
  import com.sun.java.swing.event.*;

  public class Tester {
    public static void main (String args[]) {
      Frame f = new Frame("Tester");
      DraggableLabel north = new DraggableLabel(
       "One");
      DraggableLabel south = new DraggableLabel(
       "Two");
      DraggableLabel east  = new DraggableLabel(
       "Three");
      DraggableLabel west  = new DraggableLabel(
       "Four");
      DroppableList  list  = new DroppableList();
      JScrollPane pane = new JScrollPane (list);
      f.add (north, BorderLayout.NORTH);
      f.add (south, BorderLayout.SOUTH);
      f.add (east,  BorderLayout.EAST);
      f.add (west,  BorderLayout.WEST);
      f.add (pane,  BorderLayout.CENTER);
      f.setSize (300, 300);
      f.addWindowListener (new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
      f.setVisible (true);
    }
  }

Summary

This chapter described how you deal with transferring data, both with clipboards and with drag and drop operations. While the tasks are definitely different, they share several common characteristics. By understanding the Transferable interface, as well as the DataFlavor way of dealing with the data, you should have no problem with either operation.

About the Author

John Zukowski is a Software Mage with MageLang Institute. In addition to being the author of Mastering Java 2, Java AWT Reference, and Borland JBuilder: No Experience Required. John also serves as the Focus on Java Guide at The Mining Co.


Reader Feedback

Tell us what you think of this book excerpt.

[Duke]

 Very worth reading  Worth reading  Not worth reading

If you have other comments or ideas for future book excerpts, please type them here:


Print Button
[ This page was updated: 27-Sep-2000 ]
Products & APIs | Developer Connection | Docs & Training | Online Support
Community Discussion | Industry News | Solutions Marketplace | Case Studies
Glossary | Feedback | A-Z Index
For more information on Java technology
and other software from Sun Microsystems, call:
(800) 786-7638
Outside the U.S. and Canada, dial your country's AT&T Direct Access Number first.
Sun Microsystems, Inc.
Copyright © 1995-2000 Sun Microsystems, Inc.
All Rights Reserved. Terms of Use. Privacy Policy.