FileTreeDropTarget.java Source code

Java tutorial

Introduction

Here is the source code for FileTreeDropTarget.java

Source

/*
Core SWING Advanced Programming 
By Kim Topley
ISBN: 0 13 083292 8       
Publisher: Prentice Hall  
*/

import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.Autoscroll;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;

public class FileTreeDropTarget implements DropTargetListener, PropertyChangeListener {
    public FileTreeDropTarget(FileTree tree) {
        this.tree = tree;

        // Listen for changes in the enabled property
        tree.addPropertyChangeListener(this);

        // Create the DropTarget and register 
        // it with the FileTree.
        dropTarget = new DropTarget(tree, DnDConstants.ACTION_COPY_OR_MOVE, this, tree.isEnabled(), null);
    }

    // Implementation of the DropTargetListener interface
    public void dragEnter(DropTargetDragEvent dtde) {
        DnDUtils.debugPrintln("dragEnter, drop action = " + DnDUtils.showActions(dtde.getDropAction()));

        // Save the list of selected items
        saveTreeSelection();

        // Get the type of object being transferred and determine
        // whether it is appropriate.
        checkTransferType(dtde);

        // Accept or reject the drag.
        boolean acceptedDrag = acceptOrRejectDrag(dtde);

        // Do drag-under feedback
        dragUnderFeedback(dtde, acceptedDrag);
    }

    public void dragExit(DropTargetEvent dte) {
        DnDUtils.debugPrintln("DropTarget dragExit");

        // Do drag-under feedback
        dragUnderFeedback(null, false);

        // Restore the original selections
        restoreTreeSelection();
    }

    public void dragOver(DropTargetDragEvent dtde) {
        DnDUtils.debugPrintln("DropTarget dragOver, drop action = " + DnDUtils.showActions(dtde.getDropAction()));

        // Accept or reject the drag
        boolean acceptedDrag = acceptOrRejectDrag(dtde);

        // Do drag-under feedback
        dragUnderFeedback(dtde, acceptedDrag);
    }

    public void dropActionChanged(DropTargetDragEvent dtde) {
        DnDUtils.debugPrintln(
                "DropTarget dropActionChanged, drop action = " + DnDUtils.showActions(dtde.getDropAction()));

        // Accept or reject the drag
        boolean acceptedDrag = acceptOrRejectDrag(dtde);

        // Do drag-under feedback
        dragUnderFeedback(dtde, acceptedDrag);
    }

    public void drop(DropTargetDropEvent dtde) {
        DnDUtils.debugPrintln("DropTarget drop, drop action = " + DnDUtils.showActions(dtde.getDropAction()));

        // Check the drop action
        if ((dtde.getDropAction() & DnDConstants.ACTION_COPY_OR_MOVE) != 0) {
            // Accept the drop and get the transfer data
            dtde.acceptDrop(dtde.getDropAction());
            Transferable transferable = dtde.getTransferable();
            boolean dropSucceeded = false;

            try {
                tree.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));

                // Save the user's selections
                saveTreeSelection();

                dropSucceeded = dropFile(dtde.getDropAction(), transferable, dtde.getLocation());

                DnDUtils.debugPrintln("Drop completed, success: " + dropSucceeded);
            } catch (Exception e) {
                DnDUtils.debugPrintln("Exception while handling drop " + e);
            } finally {
                tree.setCursor(Cursor.getDefaultCursor());

                // Restore the user's selections
                restoreTreeSelection();
                dtde.dropComplete(dropSucceeded);
            }
        } else {
            DnDUtils.debugPrintln("Drop target rejected drop");
            dtde.dropComplete(false);
        }
    }

    // PropertyChangeListener interface
    public void propertyChange(PropertyChangeEvent evt) {
        String propertyName = evt.getPropertyName();
        if (propertyName.equals("enabled")) {
            // Enable the drop target if the FileTree is enabled
            // and vice versa.      
            dropTarget.setActive(tree.isEnabled());
        }
    }

    // Internal methods start here

    protected boolean acceptOrRejectDrag(DropTargetDragEvent dtde) {
        int dropAction = dtde.getDropAction();
        int sourceActions = dtde.getSourceActions();
        boolean acceptedDrag = false;

        DnDUtils.debugPrintln("\tSource actions are " + DnDUtils.showActions(sourceActions) + ", drop action is "
                + DnDUtils.showActions(dropAction));

        Point location = dtde.getLocation();
        boolean acceptableDropLocation = isAcceptableDropLocation(location);

        // Reject if the object being transferred 
        // or the operations available are not acceptable.
        if (!acceptableType || (sourceActions & DnDConstants.ACTION_COPY_OR_MOVE) == 0) {
            DnDUtils.debugPrintln("Drop target rejecting drag");
            dtde.rejectDrag();
        } else if (!tree.isEditable()) {
            // Can't drag to a read-only FileTree
            DnDUtils.debugPrintln("Drop target rejecting drag");
            dtde.rejectDrag();
        } else if (!acceptableDropLocation) {
            // Can only drag to writable directory
            DnDUtils.debugPrintln("Drop target rejecting drag");
            dtde.rejectDrag();
        } else if ((dropAction & DnDConstants.ACTION_COPY_OR_MOVE) == 0) {
            // Not offering copy or move - suggest a copy
            DnDUtils.debugPrintln("Drop target offering COPY");
            dtde.acceptDrag(DnDConstants.ACTION_COPY);
            acceptedDrag = true;
        } else {
            // Offering an acceptable operation: accept
            DnDUtils.debugPrintln("Drop target accepting drag");
            dtde.acceptDrag(dropAction);
            acceptedDrag = true;
        }

        return acceptedDrag;
    }

    protected void dragUnderFeedback(DropTargetDragEvent dtde, boolean acceptedDrag) {
        if (dtde != null && acceptedDrag) {
            Point location = dtde.getLocation();
            if (isAcceptableDropLocation(location)) {
                tree.setSelectionRow(tree.getRowForLocation(location.x, location.y));
            } else {
                tree.clearSelection();
            }
        } else {
            tree.clearSelection();
        }
    }

    protected void checkTransferType(DropTargetDragEvent dtde) {
        // Accept a list of files
        acceptableType = false;
        if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
            acceptableType = true;
        }
        DnDUtils.debugPrintln("Data type acceptable - " + acceptableType);
    }

    // This method handles a drop for a list of files
    protected boolean dropFile(int action, Transferable transferable, Point location)
            throws IOException, UnsupportedFlavorException, MalformedURLException {
        List files = (List) transferable.getTransferData(DataFlavor.javaFileListFlavor);
        TreePath treePath = tree.getPathForLocation(location.x, location.y);
        File targetDirectory = findTargetDirectory(location);
        if (treePath == null || targetDirectory == null) {
            return false;
        }
        FileTree.FileTreeNode node = (FileTree.FileTreeNode) treePath.getLastPathComponent();

        // Highlight the drop location while we perform the drop
        tree.setSelectionPath(treePath);

        // Get File objects for all files being
        // transferred, eliminating duplicates.
        File[] fileList = getFileList(files);

        // Don't overwrite files by default
        copyOverExistingFiles = false;

        // Copy or move each source object to the target
        for (int i = 0; i < fileList.length; i++) {
            File f = fileList[i];
            if (f.isDirectory()) {
                transferDirectory(action, f, targetDirectory, node);
            } else {
                try {
                    transferFile(action, fileList[i], targetDirectory, node);
                } catch (IllegalStateException e) {
                    // Cancelled by user
                    return false;
                }
            }
        }

        return true;
    }

    protected File findTargetDirectory(Point location) {
        TreePath treePath = tree.getPathForLocation(location.x, location.y);
        if (treePath != null) {
            FileTree.FileTreeNode node = (FileTree.FileTreeNode) treePath.getLastPathComponent();
            // Only allow a drop on a writable directory
            if (node.isDir()) {
                try {
                    File f = new File(node.getFullName());
                    if (f.canWrite()) {
                        return f;
                    }
                } catch (Exception e) {
                }
            }
        }
        return null;
    }

    protected boolean isAcceptableDropLocation(Point location) {
        return findTargetDirectory(location) != null;
    }

    protected void saveTreeSelection() {
        selections = tree.getSelectionPaths();
        leadSelection = tree.getLeadSelectionPath();
        tree.clearSelection();
    }

    protected void restoreTreeSelection() {
        tree.setSelectionPaths(selections);

        // Restore the lead selection
        if (leadSelection != null) {
            tree.removeSelectionPath(leadSelection);
            tree.addSelectionPath(leadSelection);
        }
    }

    // Get the list of files being transferred and
    // remove any duplicates. For example, if the
    // list contains /a/b/c and /a/b/c/d, the
    // second entry is removed.
    protected File[] getFileList(List files) {
        int size = files.size();

        // Get the files into an array for sorting
        File[] f = new File[size];
        Iterator iter = files.iterator();
        int count = 0;
        while (iter.hasNext()) {
            f[count++] = (File) iter.next();
        }

        // Sort the files into alphabetical order
        // based on pathnames.
        Arrays.sort(f, new Comparator() {
            public boolean equals(Object o1) {
                return false;
            }

            public int compare(Object o1, Object o2) {
                return ((File) o1).getAbsolutePath().compareTo(((File) o2).getAbsolutePath());
            }
        });

        // Remove duplicates, retaining the results in a Vector
        Vector v = new Vector();
        char separator = System.getProperty("file.separator").charAt(0);
        outer: for (int i = f.length - 1; i >= 0; i--) {
            String secondPath = f[i].getAbsolutePath();
            int secondLength = secondPath.length();
            for (int j = i - 1; j >= 0; j--) {
                String firstPath = f[j].getAbsolutePath();
                int firstLength = firstPath.length();
                if (secondPath.startsWith(firstPath) && firstLength != secondLength
                        && secondPath.charAt(firstLength) == separator) {
                    continue outer;
                }
            }
            v.add(f[i]);
        }

        // Copy the retained files into an array
        f = new File[v.size()];
        v.copyInto(f);

        return f;
    }

    // Copy or move a file 
    protected void transferFile(int action, File srcFile, File targetDirectory, FileTree.FileTreeNode targetNode) {
        DnDUtils.debugPrintln((action == DnDConstants.ACTION_COPY ? "Copy" : "Move") + " file "
                + srcFile.getAbsolutePath() + " to " + targetDirectory.getAbsolutePath());

        // Create a File entry for the target
        String name = srcFile.getName();
        File newFile = new File(targetDirectory, name);
        if (newFile.exists()) {
            // Already exists - is it the same file?
            if (newFile.equals(srcFile)) {
                // Exactly the same file - ignore
                return;
            }
            // File of this name exists in this directory
            if (copyOverExistingFiles == false) {
                int res = JOptionPane.showOptionDialog(tree,
                        "A file called\n   " + name + "\nalready exists in the directory\n   "
                                + targetDirectory.getAbsolutePath() + "\nOverwrite it?",
                        "File Exists", JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null,
                        new String[] { "Yes", "Yes to All", "No", "Cancel" }, "No");
                switch (res) {
                case 1: // Yes to all
                    copyOverExistingFiles = true;
                case 0: // Yes
                    break;
                case 2: // No
                    return;
                default: // Cancel
                    throw new IllegalStateException("Cancelled");
                }
            }
        } else {
            // New file - create it
            try {
                newFile.createNewFile();
            } catch (IOException e) {
                JOptionPane.showMessageDialog(tree, "Failed to create new file\n  " + newFile.getAbsolutePath(),
                        "File Creation Failed", JOptionPane.ERROR_MESSAGE);
                return;
            }
        }

        // Copy the data and close file.
        BufferedInputStream is = null;
        BufferedOutputStream os = null;

        try {
            is = new BufferedInputStream(new FileInputStream(srcFile));
            os = new BufferedOutputStream(new FileOutputStream(newFile));
            int size = 4096;
            byte[] buffer = new byte[size];
            int len;
            while ((len = is.read(buffer, 0, size)) > 0) {
                os.write(buffer, 0, len);
            }
        } catch (IOException e) {
            JOptionPane.showMessageDialog(tree,
                    "Failed to copy file\n  " + name + "\nto directory\n  " + targetDirectory.getAbsolutePath(),
                    "File Copy Failed", JOptionPane.ERROR_MESSAGE);
            return;
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
                if (os != null) {
                    os.close();
                }
            } catch (IOException e) {
            }
        }

        // Remove the source if this is a move operation.
        if (action == DnDConstants.ACTION_MOVE && System.getProperty("DnDExamples.allowRemove") != null) {
            srcFile.delete();
        }

        // Update the tree display
        if (targetNode != null) {
            tree.addNode(targetNode, name);
        }
    }

    protected void transferDirectory(int action, File srcDir, File targetDirectory,
            FileTree.FileTreeNode targetNode) {
        DnDUtils.debugPrintln((action == DnDConstants.ACTION_COPY ? "Copy" : "Move") + " directory "
                + srcDir.getAbsolutePath() + " to " + targetDirectory.getAbsolutePath());

        // Do not copy a directory into itself or 
        // a subdirectory of itself.
        File parentDir = targetDirectory;
        while (parentDir != null) {
            if (parentDir.equals(srcDir)) {
                DnDUtils.debugPrintln("-- SUPPRESSED");
                return;
            }
            parentDir = parentDir.getParentFile();
        }

        // Copy the directory itself, then its contents

        // Create a File entry for the target
        String name = srcDir.getName();
        File newDir = new File(targetDirectory, name);
        if (newDir.exists()) {
            // Already exists - is it the same directory?
            if (newDir.equals(srcDir)) {
                // Exactly the same file - ignore
                return;
            }
        } else {
            // Directory does not exist - create it
            if (newDir.mkdir() == false) {
                // Failed to create - abandon this directory
                JOptionPane.showMessageDialog(tree,
                        "Failed to create target directory\n  " + newDir.getAbsolutePath(),
                        "Directory creation Failed", JOptionPane.ERROR_MESSAGE);
                return;
            }
        }

        // Add a node for the new directory
        if (targetNode != null) {
            targetNode = tree.addNode(targetNode, name);
        }

        // Now copy the directory content.
        File[] files = srcDir.listFiles();
        for (int i = 0; i < files.length; i++) {
            File f = files[i];
            if (f.isFile()) {
                transferFile(action, f, newDir, targetNode);
            } else if (f.isDirectory()) {
                transferDirectory(action, f, newDir, targetNode);
            }
        }

        // Remove the source directory after moving
        if (action == DnDConstants.ACTION_MOVE && System.getProperty("DnDExamples.allowRemove") != null) {
            srcDir.delete();
        }
    }

    public static void main(String[] args) {
        try {
            UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
        } catch (Exception evt) {
        }

        final JFrame f = new JFrame("FileTree Drop Target Example");

        try {
            final FileTree tree = new FileTree("D:\\");

            // Add a drop target to the FileTree
            FileTreeDropTarget target = new FileTreeDropTarget(tree);

            tree.setEditable(true);

            f.addWindowListener(new WindowAdapter() {
                public void windowClosing(WindowEvent evt) {
                    System.exit(0);
                }
            });

            JPanel panel = new JPanel();
            final JCheckBox editable = new JCheckBox("Editable");
            editable.setSelected(true);
            panel.add(editable);
            editable.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent evt) {
                    tree.setEditable(editable.isSelected());
                }
            });

            final JCheckBox enabled = new JCheckBox("Enabled");
            enabled.setSelected(true);
            panel.add(enabled);
            enabled.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent evt) {
                    tree.setEnabled(enabled.isSelected());
                }
            });

            f.getContentPane().add(new JScrollPane(tree), BorderLayout.CENTER);
            f.getContentPane().add(panel, BorderLayout.SOUTH);
            f.setSize(500, 400);
            f.setVisible(true);
        } catch (Exception e) {
            System.out.println("Failed to build GUI: " + e);
        }
    }

    protected FileTree tree;
    protected DropTarget dropTarget;
    protected boolean acceptableType; // Indicates whether data is acceptable
    TreePath[] selections; // Initially selected rows
    TreePath leadSelection; // Initial lead selection
    boolean copyOverExistingFiles;
}

class FileTree extends JTree implements Autoscroll {
    public static final Insets defaultScrollInsets = new Insets(8, 8, 8, 8);
    protected Insets scrollInsets = defaultScrollInsets;

    public FileTree(String path) throws FileNotFoundException, SecurityException {
        super((TreeModel) null); // Create the JTree itself

        // Use horizontal and vertical lines
        putClientProperty("JTree.lineStyle", "Angled");

        // Create the first node
        FileTreeNode rootNode = new FileTreeNode(null, path);

        // Populate the root node with its subdirectories
        boolean addedNodes = rootNode.populateDirectories(true);
        setModel(new DefaultTreeModel(rootNode));

        // Listen for Tree Selection Events
        addTreeExpansionListener(new TreeExpansionHandler());
    }

    // Returns the full pathname for a path, or null if not a known path
    public String getPathName(TreePath path) {
        Object o = path.getLastPathComponent();
        if (o instanceof FileTreeNode) {
            return ((FileTreeNode) o).fullName;
        }
        return null;
    }

    // Adds a new node to the tree after construction.
    // Returns the inserted node, or null if the parent
    // directory has not been expanded.
    public FileTreeNode addNode(FileTreeNode parent, String name) {
        int index = parent.addNode(name);
        if (index != -1) {
            ((DefaultTreeModel) getModel()).nodesWereInserted(parent, new int[] { index });
            return (FileTreeNode) parent.getChildAt(index);
        }

        // No node was created
        return null;
    }

    // Autoscrolling support
    public void setScrollInsets(Insets insets) {
        this.scrollInsets = insets;
    }

    public Insets getScrollInsets() {
        return scrollInsets;
    }

    // Implementation of Autoscroll interface
    public Insets getAutoscrollInsets() {
        Rectangle r = getVisibleRect();
        Dimension size = getSize();
        Insets i = new Insets(r.y + scrollInsets.top, r.x + scrollInsets.left,
                size.height - r.y - r.height + scrollInsets.bottom,
                size.width - r.x - r.width + scrollInsets.right);
        return i;
    }

    public void autoscroll(Point location) {
        JScrollPane scroller = (JScrollPane) SwingUtilities.getAncestorOfClass(JScrollPane.class, this);
        if (scroller != null) {
            JScrollBar hBar = scroller.getHorizontalScrollBar();
            JScrollBar vBar = scroller.getVerticalScrollBar();
            Rectangle r = getVisibleRect();
            if (location.x <= r.x + scrollInsets.left) {
                // Need to scroll left
                hBar.setValue(hBar.getValue() - hBar.getUnitIncrement(-1));
            }
            if (location.y <= r.y + scrollInsets.top) {
                // Need to scroll up
                vBar.setValue(vBar.getValue() - vBar.getUnitIncrement(-1));
            }
            if (location.x >= r.x + r.width - scrollInsets.right) {
                // Need to scroll right
                hBar.setValue(hBar.getValue() + hBar.getUnitIncrement(1));
            }
            if (location.y >= r.y + r.height - scrollInsets.bottom) {
                // Need to scroll down
                vBar.setValue(vBar.getValue() + vBar.getUnitIncrement(1));
            }
        }
    }

    // Inner class that represents a node in this file system tree
    public static class FileTreeNode extends DefaultMutableTreeNode {
        public FileTreeNode(String parent, String name) throws SecurityException, FileNotFoundException {
            this.name = name;

            // See if this node exists and whether it is a directory
            fullName = parent == null ? name : parent + File.separator + name;

            File f = new File(fullName);
            if (f.exists() == false) {
                throw new FileNotFoundException("File " + fullName + " does not exist");
            }

            isDir = f.isDirectory();

            // Hack for Windows which doesn't consider a drive to be a directory!
            if (isDir == false && f.isFile() == false) {
                isDir = true;
            }
        }

        // Override isLeaf to check whether this is a directory
        public boolean isLeaf() {
            return !isDir;
        }

        // Override getAllowsChildren to check whether this is a directory
        public boolean getAllowsChildren() {
            return isDir;
        }

        // Return whether this is a directory
        public boolean isDir() {
            return isDir;
        }

        // Get full path
        public String getFullName() {
            return fullName;
        }

        // For display purposes, we return our own name
        public String toString() {
            return name;
        }

        // If we are a directory, scan our contents and populate 
        // with children. In addition, populate those children
        // if the "descend" flag is true. We only descend once,
        // to avoid recursing the whole subtree. 
        // Returns true if some nodes were added
        boolean populateDirectories(boolean descend) {
            boolean addedNodes = false;

            // Do this only once
            if (populated == false) {
                File f;
                try {
                    f = new File(fullName);
                } catch (SecurityException e) {
                    populated = true;
                    return false;
                }

                if (interim == true) {
                    // We have had a quick look here before:
                    // remove the dummy node that we added last time
                    removeAllChildren();
                    interim = false;
                }

                String[] names = f.list(); // Get list of contents

                // Process the contents
                ArrayList list = new ArrayList();
                for (int i = 0; i < names.length; i++) {
                    String name = names[i];
                    File d = new File(fullName, name);
                    try {
                        FileTreeNode node = new FileTreeNode(fullName, name);
                        list.add(node);
                        if (descend && d.isDirectory()) {
                            node.populateDirectories(false);
                        }
                        addedNodes = true;
                        if (descend == false) {
                            // Only add one node if not descending
                            break;
                        }
                    } catch (Throwable t) {
                        // Ignore phantoms or access problems
                    }
                }

                if (addedNodes == true) {
                    // Now sort the list of contained files and directories
                    Object[] nodes = list.toArray();
                    Arrays.sort(nodes, new Comparator() {
                        public boolean equals(Object o) {
                            return false;
                        }

                        public int compare(Object o1, Object o2) {
                            FileTreeNode node1 = (FileTreeNode) o1;
                            FileTreeNode node2 = (FileTreeNode) o2;

                            // Directories come first
                            if (node1.isDir != node2.isDir) {
                                return node1.isDir ? -1 : +1;
                            }

                            // Both directories or both files -
                            // compare based on pathname
                            return node1.fullName.compareTo(node2.fullName);
                        }
                    });

                    // Add sorted items as children of this node
                    for (int j = 0; j < nodes.length; j++) {
                        this.add((FileTreeNode) nodes[j]);
                    }
                }

                // If we were scanning to get all subdirectories,
                // or if we found no content, there is no
                // reason to look at this directory again, so
                // set populated to true. Otherwise, we set interim
                // so that we look again in the future if we need to
                if (descend == true || addedNodes == false) {
                    populated = true;
                } else {
                    // Just set interim state
                    interim = true;
                }
            }
            return addedNodes;
        }

        // Adding a new file or directory after
        // constructing the FileTree. Returns
        // the index of the inserted node.
        public int addNode(String name) {
            // If not populated yet, do nothing
            if (populated == true) {
                // Do not add a new node if
                // the required node is already there
                int childCount = getChildCount();
                for (int i = 0; i < childCount; i++) {
                    FileTreeNode node = (FileTreeNode) getChildAt(i);
                    if (node.name.equals(name)) {
                        // Already exists - ensure
                        // we repopulate
                        if (node.isDir()) {
                            node.interim = true;
                            node.populated = false;
                        }
                        return -1;
                    }
                }

                // Add a new node
                try {
                    FileTreeNode node = new FileTreeNode(fullName, name);
                    add(node);
                    return childCount;
                } catch (Exception e) {
                }
            }
            return -1;
        }

        protected String name; // Name of this component
        protected String fullName; // Full pathname
        protected boolean populated;// true if we have been populated
        protected boolean interim; // true if we are in interim state
        protected boolean isDir; // true if this is a directory
    }

    // Inner class that handles Tree Expansion Events
    protected class TreeExpansionHandler implements TreeExpansionListener {
        public void treeExpanded(TreeExpansionEvent evt) {
            TreePath path = evt.getPath(); // The expanded path
            JTree tree = (JTree) evt.getSource(); // The tree

            // Get the last component of the path and
            // arrange to have it fully populated.
            FileTreeNode node = (FileTreeNode) path.getLastPathComponent();
            if (node.populateDirectories(true)) {
                ((DefaultTreeModel) tree.getModel()).nodeStructureChanged(node);
            }
        }

        public void treeCollapsed(TreeExpansionEvent evt) {
            // Nothing to do
        }
    }
}

class DnDUtils {
    public static String showActions(int action) {
        String actions = "";
        if ((action & (DnDConstants.ACTION_LINK | DnDConstants.ACTION_COPY_OR_MOVE)) == 0) {
            return "None";
        }

        if ((action & DnDConstants.ACTION_COPY) != 0) {
            actions += "Copy ";
        }

        if ((action & DnDConstants.ACTION_MOVE) != 0) {
            actions += "Move ";
        }

        if ((action & DnDConstants.ACTION_LINK) != 0) {
            actions += "Link";
        }

        return actions;
    }

    public static boolean isDebugEnabled() {
        return debugEnabled;
    }

    public static void debugPrintln(String s) {
        if (debugEnabled) {
            System.out.println(s);
        }
    }

    private static boolean debugEnabled = (System.getProperty("DnDExamples.debug") != null);
}