|
In This Issue
Welcome to the Core Java Technologies Tech Tips for June 2007. Core Java Technologies Tech Tips provides tips and hints for using core Java technologies and APIs in the Java Platform, Standard Edition 6 (Java SE 6)
In this issue provides tips for the following:
» Cookie Handling
» Drag and Drop
These tips were developed using Java SE 6. You can download Java SE 6 from the Java SE Downloads page.
The author of this month's tips is John Zukowski, president and principal consultant of JZ Ventures, Inc.
Cookie Handling in Java SE 6
Back in September 2005, the Cookie Management with CookieHandler tip described the J2SE 5.0 support for managing cookies over network connections. The 5.0 release provided the framework for dealing with these little nuggets of information, but the task of doing all the work was left to you, the developer. You only had to follow the defined API, but there was much work that needed to be done to actually get cookie management going well for your programs.
A quick look back to the J2SE 5.0 API brings you right to the abstract CookieHandler class but with no real implementation, no storage mechanism, no storage policy, and nothing to store. Jump ahead to Java SE 6, and the CookieManager class offers just such an implementation of CookieHandler. CookieStore is the storage mechanism, and CookiePolicy offers a policy for accepting or rejecting cookies. Lastly, HttpCookie is the object to store.
What used to be a difficult though doable task in J2SE 5.0, becomes simple construction of existing classes in Java SE 6.
Here's the working J2SE 5.0 program for working with cookies:
import java.io.*;
import java.net.*;
import java.util.*;
public class Fetch {
public static void main(String args[]) throws Exception {
if (args.length == 0) {
System.err.println("URL missing");
System.exit(-1);
}
String urlString = args[0];
CookieHandler.setDefault(new ListCookieHandler());
URL url = new URL(urlString);
URLConnection connection = url.openConnection();
Object obj = connection.getContent();
url = new URL(urlString);
connection = url.openConnection();
obj = connection.getContent();
}
}
|
Notice the setDefault method call to CookieHandler in the middle. The ListCookieHandler class was the tip's implementation of a basic CookieHandler.
The Java SE 6 version of the same program is nearly identical but has just one change to the following line:
CookieHandler.setDefault(new CookieManager());
|
You don't need to provide any additional code. It is really that simple to go between the different APIs. There are some other differences, but changing the one line in the Fetch program has the second network connection use the cookies returned from the first.
Now for the differences. Java SE 6 has a CookiePolicy which offers the CookieManager the ability to accept all cookies, accept no cookies, or only accept cookies from the original host. To setup the CookieManager with a different policy, you use one of the constants in the CookiePolicy interface: ACCEPT_ALL, ACCEPT_NONE, or ACCEPT_ORIGINAL_SERVER. The last option is the default when none is set. To configure the cookie manager, just call its setCookiePolicy method with the appropriate constant as shown here:
CookieManager manager = new CookieManager();
manager.setCookiePolicy(CookiePolicy.ACCEPT_NONE);
CookieHandler.setDefault(manager);
|
In addition, you can call the second constructor for CookieManager that accepts both a CookieStore and CookiePolicy argument:
CookieManager(CookieStore store, CookiePolicy cookiePolicy)
|
Passing in a null store will have the system use the predefined in-memory version. You can also define your own implementation of the interface if you wish to offer longer term storage of cookies.
Before showing the corrected version of the Fetch program, there is one more API difference to mention: how to get the list of cookies from the cookie jar, err..., manager. The CookieManager class has a getCookieStore method to get the CookieStore. You then ask the store for its List of cookies with the getCookies method, and can loop through the list.
Here's a modified version of the earlier program that utilizes the Java SE 6 version of the cookie handling API. While the basic program hasn't changed much, the need for all the supporting custom classes is now no longer necessary.
import java.io.*;
import java.net.*;
import java.util.*;
public class Fetch {
public static void main(String args[]) throws Exception {
Console console = System.console();
if (args.length == 0) {
System.err.println("URL missing");
System.exit(-1);
}
String urlString = args[0];
CookieManager manager = new CookieManager();
manager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
CookieHandler.setDefault(manager);
URL url = new URL(urlString);
URLConnection connection = url.openConnection();
Object obj = connection.getContent();
url = new URL(urlString);
connection = url.openConnection();
obj = connection.getContent();
CookieStore cookieJar = manager.getCookieStore();
List<HttpCookie> cookies = cookieJar.getCookies();
for (HttpCookie cookie: cookies) {
console.printf("Cookie: %s%n", cookie);
}
}
}
|
Just compile and run the program to see if visiting a particular URL will leave a cookie on your system. If you don't enable ACCEPT_ALL, visiting http://www.sun.com will not show any cookies, but enabling all will show three. Your run will produce different results.
> java Fetch http://www.sun.com
Cookie: JROUTE=9999
Cookie: JSESSIONID=999999995d3f13ffffffffc68433979b7f5b0
Cookie: Starload=star-fep7
|
Yes, going from the J2SE 5.0 cookie handling mechanism to Java SE 6 is that easy. You don't have to implement even one additional interface if the default functionality is sufficient. For a refresher on the difficulties involved in creating the J2SE 5.0 version of the handler, be sure to revisit the earlier tip. Be sure to try out the Tech Tips Quiz, found later in that same issue.
Adding Drop Support with JTree
Over the ages, drag and drop with the Swing component set has changed considerably. Early versions had a basic API in the java.awt.dnd package (with support from java.awt.datatransfer), but you had to define all aspects of the drag gesture, from the initial user clicking to the drop operation. J2SE 1.4 updates to the API improved upon the feature set and was described in an earlier tip: Dragging Text and Images with Swing.
The earlier API changes made most of the drag and drop tasks much easier because many components had built-in support for drag and drop tasks. For instance, to enable drag operations in a JTextField, you just have to call setDragEnabled(true) on the text component. And, users could then drag text out of a text component into some other application that acted as a drop zone, or even within the text field itself.
The text components offer built-in drop support, as does the JColorChooser component, but adding drop support to any of the other Swing components -- like JList, JTable, or JTree -- requires a little bit of extra work. The task might sound complicated, but thanks to the help of the new to 1.6 inner DropLocation class of TransferHandler, the task is relatively easy. All you have to do is create a TransferHandler for the JTree that defines what kind of data is droppable on it and what to do with it once dropped. Those operations are provided by the canImport and importData methods, respectively. TheTransferSupport inner class is new to 1.6 and adds a simpler way to define transfer handlers.
You could create a fancy JTree that accepts images or text to put on a leaf of the tree, but the example here will just accept strings. Feel free to extend the example to images, too. To support strings, you need to define the canImport method with its TransferHandler.TransferSupport argument to check the supported data flavors (string) and operation type. TransferSupport also has a getDropLocation method to get the TransferHandler.DropLocation of the task. As long as the location is a valid spot, the canImport method should return true. Here's the method which returns true for a string-flavored, drop transfer over a non-null tree path.
public boolean canImport(TransferHandler.TransferSupport support) {
if (!support.isDataFlavorSupported(DataFlavor.stringFlavor) ||
!support.isDrop()) {
return false;
}
JTree.DropLocation dropLocation =
(JTree.DropLocation)support.getDropLocation();
return dropLocation.getPath() != null;
}
|
The JTree.DropLocation is the predefined implementation of TransferHandler.DropLocation for the JTree component. There is also a JList.DropLocation for working with JList, and another for JTree with JTree.DropLocation. There is a fourth implementation in JTextComponent.DropLocation if you don't like the default text component drop handling behavior.
The other half of adding drop support to a JTree is the importData method. The older version of the importData method -- importData(JComponent comp, Transferable t) -- is still supported, just not called directly. Newer handlers should really implement the importData(TransferHandler.TransferSupport support) version instead. In this method, you need to get the transferred data and place it in the right location in the TreePath.
Getting the transferred data hasn't really changed much going from the old importData method to the new. Instead of having a Transferable argument to the method, you just get it from the TransferSupport with the support.getTransferable method. Then, just get the data for the appropriate flavor:
Transferable transferable = support.getTransferable();
String transferData;
try {
transferData = (String)transferable.getTransferData(
DataFlavor.stringFlavor);
} catch (IOException e) {
return false;
} catch (UnsupportedFlavorException e) {
return false;
}
|
For determining the location of the drop task, use the JTree.DropLocation class. Calling the getChildIndex method of DropLocation will give you the location in the tree to add the new node. A child index value of -1 means that the user dropped the node over an empty part of the tree. For this example, this will cause the node to be added to the end. Calling the getPath method of DropLocation returns the TreePath for the drop location. To then find the parent node associated with the drop location, call the path's getLastPathComponent method.
JTree.DropLocation dropLocation =
(JTree.DropLocation)support.getDropLocation();
TreePath path = dropLocation.getPath();
int childIndex = dropLocation.getChildIndex();
if (childIndex == -1) {
childIndex = model.getChildCount(path.getLastPathComponent());
}
DefaultMutableTreeNode newNode =
new DefaultMutableTreeNode(transferData);
DefaultMutableTreeNode parentNode =
(DefaultMutableTreeNode)path.getLastPathComponent();
model.insertNodeInto(newNode, parentNode, childIndex);
|
It is also helpful to ensure the new path element is visible. The complete importData method is here:
public boolean importData(TransferHandler.TransferSupport support) {
if (!canImport(support)) {
return false;
}
JTree.DropLocation dropLocation =
(JTree.DropLocation)support.getDropLocation();
TreePath path = dropLocation.getPath();
Transferable transferable = support.getTransferable();
String transferData;
try {
transferData = (String)transferable.getTransferData(
DataFlavor.stringFlavor);
} catch (IOException e) {
return false;
} catch (UnsupportedFlavorException e) {
return false;
}
int childIndex = dropLocation.getChildIndex();
if (childIndex == -1) {
childIndex = model.getChildCount(path.getLastPathComponent());
}
DefaultMutableTreeNode newNode =
new DefaultMutableTreeNode(transferData);
DefaultMutableTreeNode parentNode =
(DefaultMutableTreeNode)path.getLastPathComponent();
model.insertNodeInto(newNode, parentNode, childIndex);
TreePath newPath = path.pathByAddingChild(newNode);
tree.makeVisible(newPath);
tree.scrollRectToVisible(tree.getPathBounds(newPath));
return true;
}
|
While we've shown a sufficient amount of detail to have a fully working droppable JTree, it is import to mention one more piece of information related to drop support, the DropMode. DropMode is an enumeration of modes related to how the component shows where the dropping is going to happen. Four supported modes are available for JTree:
DropMode.USE_SELECTION
DropMode.ON
DropMode.INSERT
DropMode.ON_OR_INSERT
However, it is important to point out that the enumeration is larger for modes specific to other components (like INSERT_COLS or INSERT_ROWS when working with a JTable).
What's the deal with the drop mode? By default, the mode is USE_SELECTION, which means no longer highlight the selected item in the JTree. Instead, use the selection mechanism to highlight the drop location. It is highly recommended that if your JTree is meant to support dropping, change the default. A better mode is ON, which lets you see both the current selection in the JTree and the potential drop location. INSERT mode allows you to insert new nodes between existing nodes, while still seeing the current selection. ON_OR_INSERT combines the latter two. The following four figures shows what the four options look like. The completed program offers a combo box of modes to try out the different behaviors.
Figure 1. Use Selection
|
Figure 2. On
|
Figure 3. Insert
|
Figure 4. On or Insert
|
The complete droppable tree program is shown next. The program includes a text area at the top for entry of text that can then be selected and dropped onto
the JTree in the middle. The drop mode is settable from the combo box on the bottom. The data model for the tree comes from the default model created when one isn't specified when creating the JTree.
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
import javax.swing.tree.*;
public class DndTree {
public static void main(String args[]) {
Runnable runner = new Runnable() {
public void run() {
JFrame f = new JFrame("D-n-D JTree");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel top = new JPanel(new BorderLayout());
JLabel dragLabel = new JLabel("Drag me:");
JTextField text = new JTextField();
text.setDragEnabled(true);
top.add(dragLabel, BorderLayout.WEST);
top.add(text, BorderLayout.CENTER);
f.add(top, BorderLayout.NORTH);
final JTree tree = new JTree();
final DefaultTreeModel model = (DefaultTreeModel)tree.getModel();
tree.setTransferHandler(new TransferHandler() {
public boolean canImport(TransferHandler.TransferSupport support) {
if (!support.isDataFlavorSupported(DataFlavor.stringFlavor) ||
!support.isDrop()) {
return false;
}
JTree.DropLocation dropLocation =
(JTree.DropLocation)support.getDropLocation();
return dropLocation.getPath() != null;
}
public boolean importData(TransferHandler.TransferSupport support) {
if (!canImport(support)) {
return false;
}
JTree.DropLocation dropLocation =
(JTree.DropLocation)support.getDropLocation();
TreePath path = dropLocation.getPath();
Transferable transferable = support.getTransferable();
String transferData;
try {
transferData = (String)transferable.getTransferData(
DataFlavor.stringFlavor);
} catch (IOException e) {
return false;
} catch (UnsupportedFlavorException e) {
return false;
}
int childIndex = dropLocation.getChildIndex();
if (childIndex == -1) {
childIndex = model.getChildCount(path.getLastPathComponent());
}
DefaultMutableTreeNode newNode =
new DefaultMutableTreeNode(transferData);
DefaultMutableTreeNode parentNode =
(DefaultMutableTreeNode)path.getLastPathComponent();
model.insertNodeInto(newNode, parentNode, childIndex);
TreePath newPath = path.pathByAddingChild(newNode);
tree.makeVisible(newPath);
tree.scrollRectToVisible(tree.getPathBounds(newPath));
return true;
}
});
JScrollPane pane = new JScrollPane(tree);
f.add(pane, BorderLayout.CENTER);
JPanel bottom = new JPanel();
JLabel comboLabel = new JLabel("DropMode");
String options[] = {"USE_SELECTION",
"ON", "INSERT", "ON_OR_INSERT"
};
final DropMode mode[] = {DropMode.USE_SELECTION,
DropMode.ON, DropMode.INSERT, DropMode.ON_OR_INSERT};
final JComboBox combo = new JComboBox(options);
combo.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
int selectedIndex = combo.getSelectedIndex();
tree.setDropMode(mode[selectedIndex]);
}
});
bottom.add(comboLabel);
bottom.add(combo);
f.add(bottom, BorderLayout.SOUTH);
f.setSize(300, 400);
f.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}
|
For additional information on the drag and drop support and data transfer APIs, please see the Introduction to Drag and Drop and Data Transfer trail in the Java Tutorial online.
Developer Assistance
Need programming advice on Java SE? Try Developer Expert
Assistance.
|