/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 * http://www.dspace.org/license/
 */

/**
 * Copyright (c) 2007, Aberystwyth University
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  - Redistributions of source code must retain the above
 *    copyright notice, this list of conditions and the
 *    following disclaimer.
 *
 *  - Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 *  - Neither the name of the Centre for Advanced Software and
 *    Intelligent Systems (CASIS) nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
package org.purl.sword.client;

import java.awt.BorderLayout;
import java.awt.Component;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTree;
import javax.swing.ToolTipManager;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;

import org.purl.sword.atom.Author;
import org.purl.sword.atom.Content;
import org.purl.sword.atom.Contributor;
import org.purl.sword.atom.Generator;
import org.purl.sword.atom.Link;
import org.purl.sword.atom.TextConstruct;
import org.purl.sword.base.Collection;
import org.purl.sword.base.DepositResponse;
import org.purl.sword.base.SWORDEntry;
import org.purl.sword.base.Service;
import org.purl.sword.base.ServiceDocument;
import org.purl.sword.base.SwordAcceptPackaging;
import org.purl.sword.base.Workspace;

/**
 * The main panel for the GUI client. This contains the top-two sub-panels: the
 * tree and the text area to show the details of the selected node.
 *
 * @author Neil Taylor
 */
public class ServicePanel extends JPanel
    implements TreeSelectionListener {
    /**
     * The top level item in the tree that lists services.
     */
    DefaultMutableTreeNode top;

    /**
     * The tree model used to display the items.
     */
    DefaultTreeModel treeModel = null;

    /**
     * Tree that holds the list of services.
     */
    private JTree services;

    /**
     * The panel that shows an HTML table with any details for the selected
     * node in the services tree.
     */
    private JEditorPane details;

    /**
     * A registered listener. This listener will be notified when there is a
     * different node selected in the service tree.
     */
    private ServiceSelectedListener listener;

    /**
     * Create a new instance of the panel.
     */
    public ServicePanel() {
        super();
        setLayout(new BorderLayout());

        top = new DefaultMutableTreeNode("Services & Posted Files");
        treeModel = new DefaultTreeModel(top);

        services = new JTree(treeModel);
        services.setCellRenderer(new ServicePostTreeRenderer());

        JScrollPane servicesPane = new JScrollPane(services,
                                                   JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                                                   JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);

        details = new JEditorPane("text/html",
                                  "<html><body><h1>Details</h1><p>This panel will show the details for the currently " +
                                      "selected item in the tree.</p></body></html>");

        JScrollPane detailsPane = new JScrollPane(details,
                                                  JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                                                  JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);

        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
                                              servicesPane,
                                              detailsPane);
        splitPane.setOneTouchExpandable(true);
        splitPane.setResizeWeight(0.5);
        splitPane.setDividerLocation(200);

        services.addTreeSelectionListener(this);
        ToolTipManager.sharedInstance().registerComponent(services);

        add(splitPane, BorderLayout.CENTER);
    }

    /**
     * Renderer that displays the icons for the tree nodes.
     *
     * @author Neil Taylor
     */
    static class ServicePostTreeRenderer extends DefaultTreeCellRenderer {
        Icon workspaceIcon;
        Icon serviceIcon;
        Icon collectionIcon;
        Icon fileIcon;

        /**
         * Initialise the renderer. Load the icons.
         */
        public ServicePostTreeRenderer() {
            ClassLoader loader = this.getClass().getClassLoader();
            workspaceIcon = new ImageIcon(loader.getResource("images/WorkspaceNodeImage.gif"));
            serviceIcon = new ImageIcon(loader.getResource("images/ServiceNodeImage.gif"));
            collectionIcon = new ImageIcon(loader.getResource("images/CollectionNodeImage.gif"));
            fileIcon = new ImageIcon(loader.getResource("images/ServiceNodeImage.gif"));
        }

        /**
         * Return the cell renderer. This will be the default tree cell renderer
         * with a different icon depending upon the type of data in the node.
         *
         * @param tree The JTree control.
         * @param value The value to display.
         * @param sel True if the node is selected.
         * @param expanded True if the node is expanded.
         * @param leaf True if the node is a leaf.
         * @param row The row.
         * @param hasFocus True if the node has focus.
         */
        public Component getTreeCellRendererComponent(
            JTree tree,
            Object value,
            boolean sel,
            boolean expanded,
            boolean leaf,
            int row,
            boolean hasFocus) {

            JComponent comp = (JComponent) super.getTreeCellRendererComponent(
                tree, value, sel,
                expanded, leaf, row,
                hasFocus);

            DefaultMutableTreeNode node =
                (DefaultMutableTreeNode) value;

            Object o = node.getUserObject();
            if (o instanceof TreeNodeWrapper) {
                TreeNodeWrapper wrapper = (TreeNodeWrapper) o;
                comp.setToolTipText(wrapper.toString());
                Object data = wrapper.getData();
                if (data instanceof Service) {
                    setIcon(serviceIcon);
                } else if (data instanceof Workspace) {
                    setIcon(workspaceIcon);
                } else if (data instanceof Collection) {
                    setIcon(collectionIcon);
                } else if (data instanceof SWORDEntry) {
                    setIcon(fileIcon);
                }
            } else {
                comp.setToolTipText(null);
            }
            return comp;
        }


    }

    /**
     * Set the service selected listener. This listener will be notified when
     * there is a selection change in the tree.
     *
     * @param listener The listener.
     */
    public void setServiceSelectedListener(ServiceSelectedListener listener) {
        this.listener = listener;
    }

    /**
     * Process the specified service document. Add the details as a new child of the
     * root of the tree.
     *
     * @param url The url used to access the service document.
     * @param doc The service document.
     */
    public void processServiceDocument(String url,
                                       ServiceDocument doc) {
        TreeNodeWrapper wrapper = null;

        Service service = doc.getService();
        wrapper = new TreeNodeWrapper(url, service);
        DefaultMutableTreeNode serviceNode = new DefaultMutableTreeNode(wrapper);
        treeModel.insertNodeInto(serviceNode, top, top.getChildCount());
        services.scrollPathToVisible(new TreePath(serviceNode.getPath()));

        // process the workspaces
        DefaultMutableTreeNode workspaceNode = null;

        Iterator<Workspace> workspaces = service.getWorkspaces();
        for (; workspaces.hasNext(); ) {
            Workspace workspace = workspaces.next();
            wrapper = new TreeNodeWrapper(workspace.getTitle(), workspace);
            workspaceNode = new DefaultMutableTreeNode(wrapper);
            treeModel.insertNodeInto(workspaceNode, serviceNode, serviceNode.getChildCount());
            services.scrollPathToVisible(new TreePath(workspaceNode.getPath()));

            DefaultMutableTreeNode collectionNode = null;
            Iterator<Collection> collections = workspace.collectionIterator();
            for (; collections.hasNext(); ) {
                Collection collection = collections.next();
                wrapper = new TreeNodeWrapper(collection.getTitle(), collection);
                collectionNode = new DefaultMutableTreeNode(wrapper);
                treeModel.insertNodeInto(collectionNode, workspaceNode, workspaceNode.getChildCount());
                services.scrollPathToVisible(new TreePath(collectionNode.getPath()));
            }
        } // for
    }

    /**
     * Holds the data for a tree node. It specifies the name that will be displayed
     * in the node, and stores associated data.
     *
     * @author Neil Taylor
     */
    static class TreeNodeWrapper {
        /**
         * The node name.
         */
        private String name;

        /**
         * The user data.
         */
        private Object userObject;

        /**
         * Create a new instance.
         *
         * @param name The name of the node.
         * @param data The data in the node.
         */
        public TreeNodeWrapper(String name, Object data) {
            this.name = name;
            this.userObject = data;
        }

        /**
         * Retrieve the data that is stored in this node.
         *
         * @return The data.
         */
        public Object getData() {
            return userObject;
        }

        /**
         * Get a string description for this node.
         */
        public String toString() {
            if (name == null || name.trim().equals("")) {
                return "Unspecified";
            }

            return name;
        }
    }

    /**
     * Respond to a changed tree selection event. Update the details panel to
     * show an appropriate message for the newly selected node. Also,
     * alert the selection listener for this panel. The listener will receive
     * a path, if a collection has been selected. Otherwise, the listener
     * will receive <code>null</code>.
     */
    public void valueChanged(TreeSelectionEvent evt) {
        // Get all nodes whose selection status has changed
        TreePath[] paths = evt.getPaths();

        for (int i = 0; i < paths.length; i++) {
            if (evt.isAddedPath(i)) {
                // process new selections
                DefaultMutableTreeNode node;
                node = (DefaultMutableTreeNode) (paths[i].getLastPathComponent());

                Object o = node.getUserObject();
                if (o instanceof TreeNodeWrapper) {
                    try {
                        TreeNodeWrapper wrapper = (TreeNodeWrapper) o;
                        Object data = wrapper.getData();
                        if (data instanceof Service) {
                            showService((Service) data);
                            alertListener(null);
                        } else if (data instanceof Workspace) {
                            showWorkspace((Workspace) data);
                            if (listener != null) {
                                alertListener(null);
                            }
                        } else if (data instanceof Collection) {
                            Collection c = (Collection) data;
                            showCollection(c);
                            alertListener(c.getLocation());
                        } else if (data instanceof SWORDEntry) {
                            showEntry((SWORDEntry) data);
                            alertListener(null);
                        } else {
                            details.setText("<html><body>unknown</body></html>");
                            alertListener(null);
                        }
                    } catch (Exception e) {
                        details.setText(
                            "<html><body>An error occurred. The message was: " + e.getMessage() + "</body></html>");
                        alertListener(null);
                        e.printStackTrace();
                    }
                } else {
                    details.setText("<html><body>please select one of the other nodes</body></html>");
                    alertListener(null);
                }
            }

        }
    }

    /**
     * Notify the listener that there has been a change to the currently selected
     * item in the tree.
     *
     * @param value The value to send to the listener.
     */
    private void alertListener(String value) {
        if (listener != null) {
            listener.selected(value);
        }
    }

    /**
     * Add a new HTML table row to the specified StringBuffer. The label is displayed in
     * the left column and the value is displayed in the right column.
     *
     * @param buffer The destination string buffer.
     * @param label The label to add.
     * @param value The corresponding value to add.
     */
    private void addTableRow(StringBuffer buffer, String label, Object value) {
        buffer.append("<tr bgcolor=\"#ffffff;\"><td>");
        buffer.append(label);
        buffer.append("</td><td>");
        buffer.append(displayableValue(value));
        buffer.append("</td></tr>");
    }

    /**
     * Show the specified service data in the details panel.
     *
     * @param service The service node to display.
     */
    private void showService(Service service) {
        StringBuffer buffer = new StringBuffer();
        buffer.append("<html>");
        buffer.append("<body>");

        buffer.append("<table border=\"1\" width=\"100%\">");
        buffer.append("<tr bgcolor=\"#69a5c8;\"><td colspan=\"2\"><font size=\"+2\">Service Summary</font></td></tr>");
        addTableRow(buffer, "SWORD Version", service.getVersion());
        addTableRow(buffer, "NoOp Support ", service.isNoOp());
        addTableRow(buffer, "Verbose Support ", service.isVerbose());

        String maxSize = "";

        // Commented out the following code as the client code is out of step with the
        // Sword 'base' library and wont compile. - Robin Taylor.
        //if ( service.maxUploadIsDefined() )
        //{
        //    maxSize = "" + service.getMaxUploadSize() + "kB";
        //}
        //else
        //{
        maxSize = "undefined";
        //}

        addTableRow(buffer, "Max File Upload Size ", maxSize);

        buffer.append("</table>");

        buffer.append("</body>");
        buffer.append("</html>");
        details.setText(buffer.toString());
    }

    /**
     * Display the workspace data in the details panel.
     *
     * @param workspace The workspace.
     */
    private void showWorkspace(Workspace workspace) {
        StringBuffer buffer = new StringBuffer();
        buffer.append("<html>");
        buffer.append("<body>");

        buffer.append("<table border=\"1\" width=\"100%\">");
        buffer
            .append("<tr bgcolor=\"#69a5c8;\"><td colspan=\"2\"><font size=\"+2\">Workspace Summary</font></td></tr>");
        addTableRow(buffer, "Workspace Title", workspace.getTitle());
        buffer.append("</table>");

        buffer.append("</body>");
        buffer.append("</html>");
        details.setText(buffer.toString());
    }

    /**
     * Return the parameter unmodified if set, or the not defined text if null
     * @param s
     * @return s or ClientConstants.NOT_DEFINED_TEXT
     */
    private Object displayableValue(Object s) {
        if (null == s) {
            return ClientConstants.NOT_DEFINED_TEXT;
        } else {
            return s;
        }
    }

    /**
     * Add a string within paragraph tags.
     *
     * @param buffer The buffer to add the message to.
     * @param message The message to add.
     */
    private void addPara(StringBuffer buffer, String message) {
        buffer.append("<p>" + message + "</p>");
    }

    /**
     * Show the specified collection data in the details panel.
     *
     * @param collection The collection data.
     */
    private void showCollection(Collection collection) {
        StringBuffer buffer = new StringBuffer();
        buffer.append("<html>");
        buffer.append("<body>");

        if (collection == null) {
            addPara(buffer, "Invalid Collection object. Unable to display details.");
        } else {
            buffer.append("<table border=\"1\" width=\"100%\">");
            buffer.append(
                "<tr bgcolor=\"#69a5c8;\"><td colspan=\"2\"><font size=\"+2\">Collection Summary</font></td></tr>");
            addTableRow(buffer, "Collection location", collection.getLocation());
            addTableRow(buffer, "Collection title", collection.getTitle());
            addTableRow(buffer, "Abstract", collection.getAbstract());
            addTableRow(buffer, "Collection Policy", collection.getCollectionPolicy());
            addTableRow(buffer, "Treatment", collection.getTreatment());
            addTableRow(buffer, "Mediation", collection.getMediation());
            addTableRow(buffer, "Nested Service Document", collection.getService());

            String[] accepts = collection.getAccepts();
            StringBuilder acceptList = new StringBuilder();
            if (accepts != null && accepts.length == 0) {
                acceptList.append("None specified");
            } else {
                for (String s : accepts) {
                    acceptList.append(s).append("<br>");
                }
            }
            addTableRow(buffer, "Accepts", acceptList.toString());

            List<SwordAcceptPackaging> acceptsPackaging = collection.getAcceptPackaging();

            StringBuilder acceptPackagingList = new StringBuilder();
            for (Iterator i = acceptsPackaging.iterator(); i.hasNext(); ) {
                SwordAcceptPackaging accept = (SwordAcceptPackaging) i.next();
                acceptPackagingList.append(accept.getContent()).append(" (").append(accept.getQualityValue())
                                   .append(")");

                // add a , separator if there are any more items in the list
                if (i.hasNext()) {
                    acceptPackagingList.append(", ");
                }
            }

            addTableRow(buffer, "Accepts Packaging", acceptPackagingList.toString());

            buffer.append("</table>");
        }

        buffer.append("</body>");
        buffer.append("</html>");
        details.setText(buffer.toString());
    }

    /**
     * Display the contents of a Post entry in the display panel.
     *
     * @param entry The entry to display.
     */
    private void showEntry(SWORDEntry entry) {
        StringBuffer buffer = new StringBuffer();
        buffer.append("<html>");
        buffer.append("<body>");

        if (entry == null) {
            addPara(buffer, "Invalid Entry object. Unable to display details.");
        } else {
            buffer.append("<table border=\"1\" width=\"100%\">");
            buffer
                .append("<tr bgcolor=\"#69a5c8;\"><td colspan=\"2\"><font size=\"+2\">Entry Summary</font></td></tr>");

            // process atom:title
            String titleString = getTextConstructDetails(entry.getSummary());
            addTableRow(buffer, "Title", titleString);

            // process id
            addTableRow(buffer, "ID", entry.getId());

            // process updated
            addTableRow(buffer, "Date Updated", entry.getUpdated());

            String authorString = getAuthorDetails(entry.getAuthors());
            addTableRow(buffer, "Authors", authorString);

            // process summary
            String summaryString = getTextConstructDetails(entry.getSummary());
            addTableRow(buffer, "Summary", summaryString);

            // process content
            Content content = entry.getContent();
            String contentString = "";
            if (content == null) {
                contentString = "Not defined.";
            } else {
                contentString += "Source: '" + content.getSource() + "', Type: '" +
                    content.getType() + "'";
            }
            addTableRow(buffer, "Content", contentString);

            // process links
            Iterator<Link> links = entry.getLinks();
            StringBuffer linkBuffer = new StringBuffer();
            for (; links.hasNext(); ) {
                Link link = links.next();
                linkBuffer.append("href: '");
                linkBuffer.append(link.getHref());
                linkBuffer.append("', href lang: '");
                linkBuffer.append(link.getHreflang());
                linkBuffer.append("', rel: '");
                linkBuffer.append(link.getRel());
                linkBuffer.append("')<br>");
            }
            if (linkBuffer.length() == 0) {
                linkBuffer.append("Not defined");
            }
            addTableRow(buffer, "Links", linkBuffer.toString());

            // process contributors
            String contributorString = getContributorDetails(entry.getContributors());
            addTableRow(buffer, "Contributors", contributorString);

            // process source
            String sourceString = "";
            Generator generator = entry.getGenerator();
            if (generator != null) {
                sourceString += "Content: '" + generator.getContent() + "' <br>'";
                sourceString += "Version: '" + generator.getVersion() + "' <br>'";
                sourceString += "Uri: '" + generator.getUri() + "'";
            } else {
                sourceString += "No generator defined.";
            }

            addTableRow(buffer, "Generator", sourceString);

            // process treatment
            addTableRow(buffer, "Treatment", entry.getTreatment());

            // process verboseDescription
            addTableRow(buffer, "Verbose Description", entry.getVerboseDescription());

            // process noOp
            addTableRow(buffer, "NoOp", entry.isNoOp());

            // process formatNamespace
            addTableRow(buffer, "Packaging", entry.getPackaging());

            // process userAgent
            addTableRow(buffer, "User Agent", entry.getUserAgent());


            buffer.append("</table>");
        }

        buffer.append("</body>");
        buffer.append("</html>");
        details.setText(buffer.toString());
    }

    /**
     * Retrieve the details for a TextConstruct object.
     *
     * @param data The text construct object to display.
     *
     * @return Either 'Not defined' if the data is <code>null</code>, or
     *         details of the text content element.
     */
    private String getTextConstructDetails(TextConstruct data) {
        String summaryStr = "";
        if (data == null) {
            summaryStr = "Not defined";
        } else {
            summaryStr = "Content: '" + data.getContent() + "', Type: ";
            if (data.getType() != null) {
                summaryStr += "'" + data.getType().toString() + "'";
            } else {
                summaryStr += "undefined.";
            }
        }

        return summaryStr;
    }

    /**
     * Get the author details and insert them into a string.
     *
     * @param authors the list of authors to process.
     *
     * @return A string containing the list of authors.
     */
    private String getAuthorDetails(Iterator<Author> authors) {
        // process author
        StringBuffer authorBuffer = new StringBuffer();
        for (; authors.hasNext(); ) {
            Author a = authors.next();
            authorBuffer.append(getAuthorDetails(a));
        }

        if (authorBuffer.length() == 0) {
            authorBuffer.append("Not defined");
        }

        return authorBuffer.toString();
    }

    /**
     * Get the contributor details and insert them into a string.
     *
     * @param contributors The contributors.
     *
     * @return The string that lists the details of the contributors.
     */
    private String getContributorDetails(Iterator<Contributor> contributors) {
        // process author
        StringBuffer authorBuffer = new StringBuffer();
        for (; contributors.hasNext(); ) {
            Contributor c = contributors.next();
            authorBuffer.append(getAuthorDetails(c));
        }

        if (authorBuffer.length() == 0) {
            authorBuffer.append("Not defined");
        }

        return authorBuffer.toString();
    }

    /**
     * Build a string that describes the specified author.
     *
     * @param author The author.
     *
     * @return The string description.
     */
    private String getAuthorDetails(Author author) {
        // process author
        StringBuffer authorBuffer = new StringBuffer();
        authorBuffer.append(author.getName());
        authorBuffer.append(" (email: '");
        authorBuffer.append(author.getEmail());
        authorBuffer.append("', uri: '");
        authorBuffer.append(author.getUri());
        authorBuffer.append("')<br>");

        return authorBuffer.toString();
    }

    /**
     * Process the deposit response and insert the details into the tree. If the url
     * matches one of the collections in the tree, the deposit is added as a child
     * node. Otherwise, the node is added as a child of the root.
     *
     * @param url The url of the collection that the file was posted to.
     *
     * @param response The details of the deposit.
     */
    public void processDepositResponse(String url,
                                       DepositResponse response) {
        SWORDEntry entry = response.getEntry();
        Object title = entry.getTitle();
        if (title == null) {
            title = "Undefined";
        } else {
            title = entry.getTitle().getContent();
        }

        TreeNodeWrapper wrapper = new TreeNodeWrapper(title.toString(), entry);
        DefaultMutableTreeNode entryNode = new DefaultMutableTreeNode(wrapper);

        DefaultMutableTreeNode newParentNode = top;
        List<DefaultMutableTreeNode> nodes = getCollectionNodes();
        for (DefaultMutableTreeNode node : nodes) {
            Object o = node.getUserObject();
            if (o instanceof TreeNodeWrapper) {
                TreeNodeWrapper collectionWrapper = (TreeNodeWrapper) o;
                Object data = collectionWrapper.getData();
                if (data instanceof Collection) {
                    Collection col = (Collection) data;
                    String location = col.getLocation();
                    if (location != null && location.equals(url)) {
                        newParentNode = node;
                        break;
                    }
                }
            }
        }

        treeModel.insertNodeInto(entryNode, newParentNode, newParentNode.getChildCount());
        services.scrollPathToVisible(new TreePath(entryNode.getPath()));
    }

    /**
     * Get a list of all current collections displayed in the tree.
     *
     * @return An array of the URLs for the collections.
     */
    public String[] getCollectionLocations() {
        List<DefaultMutableTreeNode> nodes = getCollectionNodes();
        String[] locations = new String[nodes.size()];

        DefaultMutableTreeNode node;
        for (int i = 0; i < nodes.size(); i++) {
            node = nodes.get(i);
            Object o = node.getUserObject();
            if (o instanceof TreeNodeWrapper) {
                TreeNodeWrapper collectionWrapper = (TreeNodeWrapper) o;
                Object data = collectionWrapper.getData();
                if (data instanceof Collection) {
                    Collection col = (Collection) data;
                    String location = col.getLocation();
                    if (location != null) {
                        locations[i] = location;
                    }
                }
            }
        }
        return locations;
    }

    /**
     * Get a list of nodes that contain collections.
     *
     * @return A vector of the collection nodes.
     */
    private List<DefaultMutableTreeNode> getCollectionNodes() {
        List<DefaultMutableTreeNode> nodes = new ArrayList<DefaultMutableTreeNode>();

        DefaultMutableTreeNode node;
        Enumeration treeNodes = top.depthFirstEnumeration();

        while (treeNodes.hasMoreElements()) {
            node = (DefaultMutableTreeNode) treeNodes.nextElement();
            Object o = node.getUserObject();
            if (o instanceof TreeNodeWrapper) {
                TreeNodeWrapper wrapper = (TreeNodeWrapper) o;
                Object data = wrapper.getData();
                if (data instanceof Collection) {
                    nodes.add(node);
                }
            }
        }

        return nodes;
    }
}
