/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ui;                    
   
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.TextEvent;
import java.awt.event.TextListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.EventObject;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreeSelectionModel;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

/**
 * A sample DOM Tree Viewer. This sample program illustrates how to
 * traverse a DOM tree and display it in a Swing JTree View.
 *
 * @version $Id: TreeView.java 778249 2009-05-24 22:39:58Z mrglavas $
 */
public class TreeView extends JFrame implements ActionListener, TextListener {

    private static final long serialVersionUID = 3688504394090098738L;

    //
    // Constants
    //

    static final boolean DEBUG = true;
  
    /** Default parser name. */
    static final String 
        DEFAULT_PARSER_NAME = "org.apache.xerces.parsers.DOMParser";
    static int WARNING = 0;
    static int ERROR=1;
    static int FATAL_ERROR=2;
        

    static final String title = "TreeViewer";
    static final String openString = "Open";
    static final String quitString = "Quit";
    static final String reloadString = "Reload current XML file";
    static final String expandString = "Expand Tree";
    static final String collapseString = "Collapse Tree";

    //
    // Data
    //

    ErrorStorer ef;
    String fname;
    DOMTree m_tree;
    JTextArea sourceText, messageText;
    Vector textLine;
    FileNameInput fni;
    DOMParserSaveEncoding parser;
    Image openFolder;
    Image closedFolder;
    Image leafImage;

    /**
     *  Constructor
     */
    public TreeView() {
        this(null);
    }

    /**
     *  Constructor
     */
    public TreeView(String uri) {
        super(uri);
        openFolder = DefaultImages.createOpenFolderImage();
        closedFolder = DefaultImages.createClosedFolderImage();
        leafImage = DefaultImages.createLeafImage();
        parser = new DOMParserSaveEncoding();
        ef = new ErrorStorer();
        fname = uri;
        JMenuBar jmb = new JMenuBar();
        JMenu fileMenu = new JMenu("File");
        JMenuItem item;

        item = new JMenuItem(openString);
        fileMenu.add(item);
        item.addActionListener(this);

        item = new JMenuItem(quitString);
        fileMenu.add(item);
        item.addActionListener(this);

        JMenu shortcutMenu = new JMenu("Shortcuts");

        item = new JMenuItem(expandString);
        shortcutMenu.add(item);
        item.addActionListener(this);

        item = new JMenuItem(collapseString);
        shortcutMenu.add(item);
        item.addActionListener(this);

        item = new JMenuItem(reloadString);
        shortcutMenu.add(item);
        item.addActionListener(this);

        jmb.add(fileMenu);
        jmb.add(shortcutMenu);
        setJMenuBar(jmb);

        getContentPane().add(createUI(fname));
        
    }

    /** create and return the entire UI from the root TreeNode
     */
    JComponent createUI(String filename) {
        if (DEBUG) System.out.println("START createUI:"+filename);

        // create the message panel first so we can send messages to it...
        messageText = new JTextArea(3,40);
        messageText.setFont(new Font("dialog", Font.PLAIN, 12));
        JPanel messagePanel = new JPanel(new BorderLayout());
        messagePanel.add(new JScrollPane(messageText) {
            private static final long serialVersionUID = 3978426918603075632L;
            public Dimension getPreferredSize(){
                Dimension size = TreeView.this.getSize();
                return new Dimension(size.width, size.height / 4);
                }
            public Dimension getMinimumSize(){
                return new Dimension(100, 100);
                }
            },
            BorderLayout.CENTER);
        messagePanel.setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createTitledBorder("Messages"),
            BorderFactory.createEmptyBorder(4, 4, 4, 4)
            ));

        // create the TextArea for XML source
        sourceText = new JTextArea();
        sourceText.setFont(new Font("monospaced", Font.PLAIN, 12));
        sourceText.setBackground(Color.white);
        sourceText.setForeground(Color.black);
        sourceText.setSelectedTextColor(Color.black);
        sourceText.setSelectionColor(Color.red);
        sourceText.setEditable(false);
        JPanel sourcePanel = new JPanel(new BorderLayout());
        sourcePanel.add(new JScrollPane(sourceText){
            private static final long serialVersionUID = 4121135831458068789L;
            public Dimension getPreferredSize(){
                Dimension size = TreeView.this.getSize();
                return new Dimension(size.width / 2, size.height * 3 / 5);
                }
            public Dimension getMinimumSize(){
                return new Dimension(100, 100);
                }
            },
            BorderLayout.CENTER);
        sourcePanel.setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createTitledBorder("Source View"),
            BorderFactory.createEmptyBorder(4, 4, 4, 4)
            ));

        // create the JTree and scroll pane.
        JPanel treePanel = new JPanel(new BorderLayout());
        m_tree = new DOMTree();
        m_tree.setCellRenderer(new XMLTreeCellRenderer());
        m_tree.getSelectionModel().setSelectionMode
            (TreeSelectionModel.SINGLE_TREE_SELECTION);

        // Listen for when the selection changes, call nodeSelected(node)
        m_tree.addTreeSelectionListener(
            new TreeSelectionListener() {
                public void valueChanged(TreeSelectionEvent e) {
                    TreeNode node = (TreeNode)
                        (e.getPath().getLastPathComponent());

                    nodeSelected(node);
                }
            }
        );
        m_tree.setRowHeight(18);
        m_tree.setFont(new Font("dialog", Font.PLAIN, 12));

        treePanel.add(new JScrollPane(m_tree) {
            private static final long serialVersionUID = 3977860665971126320L;
            public Dimension getPreferredSize(){
                Dimension size = TreeView.this.getSize();
                return new Dimension(size.width / 2, size.height * 3 / 5);
                }
            public Dimension getMinimumSize(){
                return new Dimension(100, 100);
                }
            },
            BorderLayout.CENTER);

        treePanel.setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createTitledBorder("Tree View"),
            BorderFactory.createEmptyBorder(4, 4, 4, 4)
            ));

        // refreshUI loads everthything!
        refreshUI(filename);

        // use the new JSplitPane to dynamically resize...
        JComponent split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
            true, treePanel, sourcePanel);

        JComponent mainSplitPane =
            new JSplitPane(JSplitPane.VERTICAL_SPLIT,
                           true, split, messagePanel);

        if (DEBUG) System.out.println("END createUI:"+filename);
        return mainSplitPane;
    }

    /** refreshUI is called when we have a new filename to parse.
     */
    void refreshUI(String filename) {
        if (DEBUG) System.out.println("START refreshUI:"+filename);

        messageText.selectAll();
        messageText.cut();

        if (filename == null || filename.length() == 0) {
            messageText.setForeground(Color.red);
            messageText.append("No input XML filename specified:"+filename+"\n");
            return;
        }

        fname = filename;
        Document newRoot = getRoot(filename);
        if (newRoot == null) {
            messageText.setForeground(Color.red);
            messageText.append("Unable to get new DOM Tree for:"+filename+"\n");
            return;
        }
        m_tree.setDocument(newRoot);

        // new Source
        sourceText.selectAll();
        sourceText.cut();
        readXMLFile(fname, sourceText);

        setTitle(title+": "+filename);

        if (m_tree!= null)
            expandTree();


        if (ef != null && ef.getErrorNodes()!=null       
                    && ef.getErrorNodes().size() > 0 ) {
            messageText.setForeground(Color.red);
            messageText.append("XML source, "+fname+" has errors.\n");
            messageText.append("Please click on red Tree View items for details.\n");
            /***/
            Hashtable errors = ef.getErrorNodes();
            Iterator entries = errors.entrySet().iterator();
            while (entries.hasNext()) {
                Map.Entry entry = (Map.Entry) entries.next();
                Node node = (Node) entry.getKey();
                ParseError parseError = (ParseError) entry.getValue();
                messageText.append("node="+node.getNodeName()
                +", error="+parseError.getMsg()+"\n");
            }
        }
        if (DEBUG) System.out.println("END refreshUI:"+filename);
    }

    /**
     *  Invoke the Parser on fname and return the root TreeNode.
     */
    public Document getRoot(String filename) {
        if (DEBUG) System.out.println("START getRoot:"+filename);

        if (filename == null || filename.length() == 0)
        return null;

        try {
            //
            // Reset the Error Storage and handling
            //
     
            ef.resetErrors();
            parser.setErrorHandler(ef);
            parser.setFeature("http://apache.org/xml/features/dom/defer-node-expansion", false); // otherwise parser.getCurrentNode() == null
            parser.setFeature("http://apache.org/xml/features/continue-after-fatal-error", true);
            parser.setFeature("http://apache.org/xml/features/allow-java-encodings", true);
            parser.parse(filename);
            Document document = parser.getDocument();
            /***/
            return document;
        } catch (Exception e) {
             System.err.println( "Error: Invalid XML document could not get ROOT" );
             System.exit( 1 );
            //e.printStackTrace(System.err);
        }
        return null;
    }

    /** read the xml file from filename and append it to the JTextArea
     */
    synchronized void readXMLFile(String filename, JTextArea ta) {

        if (DEBUG) System.out.println("START readXMLFile"+filename);
        if (filename == null || filename.length() == 0)
            return;
        InputStream fis = null;
        BufferedReader dis = null;
        try {
            java.net.URL file = createURL(filename);
            fis = file.openStream();

            String javaEncoding = parser.getJavaEncoding(); // get saved java encoding
            try
            {
            dis = new BufferedReader(new InputStreamReader(fis, javaEncoding ));
            }
            catch( UnsupportedEncodingException ex )
            {
            dis = new BufferedReader(new InputStreamReader(fis ));
            }
        } catch (Exception ex) {
            System.err.println("ERROR: Xerces.readXMLFile: "+ex);
            return;
        }

        String line;
        int len = 0;
        textLine = new Vector();
        String nl = "\n";
        int nllen = nl.length();
        StringBuffer sb = new StringBuffer();

        try{
            readline: while ((line = dis.readLine()) != null) {
                sb.append(line+nl);
                textLine.addElement(new Integer(len));
                len += line.length()+nllen;
            }
            ta.append(sb.toString());
        } catch (IOException io) {
            System.err.println(io);
            return;
        }

        // relayout because contents have changed
        //ta.revalidate();

        if (DEBUG) System.out.println("END readXMLFile"+filename);
        return;

    }

    /** called when our JTree's nodes are selected.
     */
    void nodeSelected(TreeNode treeNode) {

        Node node = m_tree.getNode(treeNode);

        if( node == null )     // It is possible to get a null node
            return;

        StringBuffer sb = new StringBuffer();
        messageText.selectAll();
        messageText.cut();


        //fix

       //JTextArea sourceText = sourceText;
       Object errorObject = ef == null ? null : ef.getError(node);
       if (errorObject != null) {
           // There *is* an error in this node.
           messageText.setForeground(Color.red);
           ParseError eip = (ParseError)errorObject;
           sb.append("Error: "+eip.getMsg()+"\n");
           int lineNo = eip.getLineNo();
           int pos  = 0;
           int next = 0;                       
           int sizeOfTextLine = textLine.size();

           if( lineNo < sizeOfTextLine )
              {
              pos = ((Integer)textLine.elementAt(lineNo-1)).intValue();
              next = (lineNo == sizeOfTextLine ) ?
               pos :
               (((Integer)textLine.elementAt(lineNo)).intValue());
              }
           else
              {
              pos  = (( Integer) textLine.elementAt( sizeOfTextLine - 1 )).intValue();
              next = pos + 2;
              }

           sourceText.select(pos, next );
           //m_textScrollPane.repaint();
       } else {
           messageText.setForeground(Color.black);
           sourceText.select(0, 0 );
       }
        
        //fix

        
        if (node.getNodeType() == Node.ELEMENT_NODE
                    || node.getNodeType() == Node.TEXT_NODE 
                || node.getNodeType() == Node.CDATA_SECTION_NODE )
                 {
                    sb.append(node.toString());
            }
       
        messageText.append(sb.toString());
    }

    /** called when a the text value has changed in the FileNameInput.
     *  read in new XML file.
     */
    public void textValueChanged(TextEvent e) {
        try {
            if (fni != null)
                fni.setVisible(false);
            fname = ((JTextField)e.getSource()).getText();
            if (DEBUG) System.out.println("textValueChanged:"+fname);
            refreshUI(fname);

        } catch (Exception ex) {
           System.err.println( "Error: while trying to refresh gui" );
           System.exit( 1 );
        //    ex.printStackTrace();
        }
    }

    /** called to handle menu actions.
     */
    public void actionPerformed(java.awt.event.ActionEvent e) {
        if (DEBUG) System.err.println("ACTION: "+e.getActionCommand()+", "+e.paramString());

        if (e.getActionCommand().equals(quitString)) {
          System.exit(0);
        }
        else if (e.getActionCommand().equals(openString)) {

            fni = new FileNameInput("Open File");
            fni.addTextListener(this);
            fni.setVisible(true);
        }
        else if (e.getActionCommand().equals(expandString)) {
            expandTree();
        }
        else if (e.getActionCommand().equals(collapseString)) {
            int rows = m_tree.getRowCount();
            for (int i = 0; i < rows; i++) {
                m_tree.collapseRow(i);
            }
        }
        else
        //if (e.getActionCommand().equals(reloadString)) {
            refreshUI(fname);
        //}
    }

    void expandTree() {
        int rows = 0;
        for (int levels=0; levels <= 4; levels++) {
            rows=m_tree.getRowCount();
            for (int i = 0; i < rows; i++) {
                m_tree.expandRow(i);
            }
        }
    }

    /*
     * The XMLTreeCellRenderer is an inner class which enables the
     * highlighting of errors in the tree and shows the gender values
     * as different icons.
     */
    class XMLTreeCellRenderer extends DefaultTreeCellRenderer {
        
        private static final long serialVersionUID = 3761130444229720113L;

        public Component getTreeCellRendererComponent(JTree tree, Object value,
                          boolean selected, boolean expanded,
                          boolean leaf, int row,
                              boolean hasFocus)
        {
            Node node = ((DOMTree)tree).getNode(value);
            Component comp = super.getTreeCellRendererComponent(tree, value,
                           selected,  expanded, leaf,  row, hasFocus);
            if (selected) {
                comp.setBackground(Color.blue);
            }
            if (ef != null
            && ef.getErrorNodes() != null
            && value != null
            && node != null
            && ef.getErrorNodes().containsKey( node )) {
                comp.setForeground(Color.red);
            }

            if (node != null) {
                if (leaf) {
                    setIcon(new ImageIcon(leafImage));
                } else if (expanded) {
                    setIcon(new ImageIcon(openFolder));
                } else {
                    setIcon(new ImageIcon(closedFolder));
                }
            }
            if (node != null && node instanceof Element) {
                
                Element txNode = (Element)node;
                Attr txAtt = (Attr)txNode.getAttributeNode("gender");
                if (txAtt != null) {
                    if (txAtt.getValue().equals("male")) {
                        setIcon(new ImageIcon("male.gif"));
                    } else
                    if (txAtt.getValue().equals("female")) {
                        setIcon(new ImageIcon("female.gif"));
                    }
                }
            }

            return comp;
        }
    }

    /*
     * The FileNameInput is an inner class which allows the user
     * to enter a filename. It exists due to a Swing bug which
     * has problems with the real file input panel.
     */
    class FileNameInput extends JFrame implements ActionListener {

        private static final long serialVersionUID = 3257562893292615472L;
        
        JLabel fileLabel;
        JTextField textField;
        JButton ok;
        JButton cancel;
        Vector textListeners;

        public FileNameInput() {
            this("");
        }

        public FileNameInput(String title) {

            super(title);

            fileLabel = new JLabel("Enter XML file name:");
            textField = new JTextField();
            textField.addActionListener(this);
            ok = new JButton("ok");
            cancel = new JButton("cancel");
            JPanel buttonPanel = new JPanel();
            buttonPanel.add(ok);
            buttonPanel.add(cancel);
            ok.addActionListener(this);
            cancel.addActionListener(this);
            getContentPane().add(fileLabel, BorderLayout.NORTH);
            getContentPane().add(textField, BorderLayout.CENTER);
            getContentPane().add(buttonPanel, BorderLayout.SOUTH);
            setSize(400,100);
        }

        public void actionPerformed(ActionEvent e) {

            if (e.getSource() == ok || e.getSource() == textField) {
                System.out.println("FileNameInput: pressed OK");
                    TextEvent event = new TextEvent(textField, TextEvent.TEXT_VALUE_CHANGED);
                    deliverEvent(event);
                    setVisible(false);
            } else
            if (e.getSource() == cancel) {
                System.out.println("FileNameInput: pressed cancel");
                    setVisible(false);
            }
        }

        /**
         * Adds a TextListener event listener.
         *
         * @param listener  The listener to add.
         *
         * @see #removeTextListener
         */
        public void addTextListener(TextListener listener) {

            // is there anything to do?
            if (listener == null)
                return;

            if (textListeners == null)
               textListeners = new Vector();

            // add listener
            textListeners.addElement(listener);
            }

        /**
         * Removes a TextListener event listener.
         *
         * @param listener  The listener to remove.
         *
         * @see #addTextListener
         */
        public void removeTextListener(TextListener listener) {

            // is there anything to do?
            if (listener == null || textListeners == null)
                return;

            // add listener
            textListeners.removeElement(listener);
            }


        /**
         * This function delivers TextListener events, when the ok
         * button is clicked.
         *
         * @param evt The event to deliver.
         */
        protected void deliverEvent(EventObject evt) {

            if (evt instanceof TextEvent) {
                TextEvent event = (TextEvent)evt;

                Vector l;
                synchronized (textListeners) { l = (Vector)textListeners.clone(); }

                int size = l.size();
                for (int i = 0; i < size; i++)
                    ((TextListener)l.elementAt(i)).textValueChanged(event);
                }
            }
    }

    //
    // Create a URL object from either a URL string or a plain file name.
    //
    static URL createURL(String name) throws Exception {
        try {
                URL u = new URL(name);
                return u;
        } catch (MalformedURLException ex) {
        }
        URL u = new URL("file:" + new File(name).getAbsolutePath());
        return u;
    }    
    
    /**
     * The ErrorStorer maps Nodes to errors. It receives a reference
     * to the ErrorTreeFactory in the Constructor.
     *
     * When error is called, it asks the
     * ErrorTreeFactory for the current node, and uses this as the
     * "key" of a Hashtable, with the error as a value. The error
     * value is wrapped up nicely in an ParseError object.
     *
     * It is used in the XML Tutorial to illustrate how to implement
     * the ErrorListener to provide error storage for later reference.
     *
     */
    class ErrorStorer 
        implements ErrorHandler
        
    {

        //
        // Data
        //
        Hashtable errorNodes = null;
        
        /**
         * Constructor
         */
        public ErrorStorer() {
        }

        /**
         * The client is is allowed to get a reference to the Hashtable,
         * and so could corrupt it, or add to it...
         */
        public Hashtable getErrorNodes() {
            return errorNodes;
        }

        /**
         * The ParseError object for the node key is returned.
         * If the node doesn't have errors, null is returned.
         */
        public Object getError(Node node) {
            if (errorNodes == null)
                return null;
            return errorNodes.get(node);
        }
        
        /**
         * Reset the error storage.
         */
        public void resetErrors() {
            if (errorNodes != null)
            errorNodes.clear();
        }
        
        /***/
        public void warning(SAXParseException ex) {
            handleError(ex, WARNING);
        }

        public void error(SAXParseException ex) {
            handleError(ex, ERROR);
        }

        public void fatalError(SAXParseException ex) throws SAXException {
            handleError(ex, FATAL_ERROR);
        }
        
        private void handleError(SAXParseException ex, int type) {
            System.out.println("!!! handleError: "+ex.getMessage());

            StringBuffer errorString = new StringBuffer();
            errorString.append("at line number, ");
            errorString.append(ex.getLineNumber());
            errorString.append(": ");
            errorString.append(ex.getMessage());

            // Node current = parser.getCurrentNode();

            Node current = null ;

            try
              {
              current = ( Node ) parser.getProperty( "http://apache.org/xml/properties/dom/current-element-node" );
              
              }
            catch( SAXException exception  )
              {
               ;
              }

            if (current == null) {
                System.err.println("Error in handleError. getCurrentNode()==null!");
                return;
            }
            
            if (errorNodes == null)
                errorNodes = new Hashtable();
            ParseError previous = (ParseError) errorNodes.get(current);
            ParseError eip  = null;
            // if a Node already has an error, we accumulate the text here...
            if (previous != null) {
                eip = previous;
                errorString = new StringBuffer(previous.getMsg()+"\n"+errorString.toString());
                eip.setMsg(errorString.toString());
            } else {
                eip = new
                    ParseError(
                        ex.getSystemId(), 
                        ex.getLineNumber(), 
                        ex.getColumnNumber(),
                        "",  
                        errorString.toString());
            }

            // put it in the Hashtable.
            errorNodes.put(current, eip);
        }
        
    }
    
    /**
     * The ParseError class wraps up all the error info from
     * the ErrorStorer's error method.
     *
     * @see ErrorStorer
     */
    class ParseError extends Object {

        //
        // Data
        //

        String fileName;
        int lineNo;
        int charOffset;
        Object key;
        String msg;

        /**
         * Constructor
         */
        public ParseError(String fileName, int lineNo, int charOffset,
                           Object key, 
                           String msg)
        {
            this. fileName=fileName;
            this. lineNo=lineNo;
            this. charOffset=charOffset;
            this. key=key;
            this. msg=msg;
        }

        //
        // Getters...
        //
        public String getFileName() { return fileName; }
        public int getLineNo() { return lineNo; }
        public int getCharOffset() { return charOffset;}
        public Object getKey() { return key; }
        public String getMsg() { return msg; }
        public void setMsg(String s) { msg = s; }
    }


    //
    // Main
    //
    
    /** Main program entry point. */
    public static void main(String argv[]) {
        
        // vars
        int parserNameIndex = -1;
        String parserName = DEFAULT_PARSER_NAME;

        // check parameters
        for (int i = 0; i < argv.length; i++) {
            String arg = argv[i];

            // options
            if (arg.startsWith("-")) {
                if (arg.equals("-p")) {
                    if (i == argv.length - 1) {
                        System.err.println("error: missing parser class");
                        System.exit(1);
                    }
                    parserName = argv[++i];
                    parserNameIndex = i;
                    continue;
                }

                if (arg.equals("-h")) {
                    printUsage();
                    System.exit(1);
                }
            }

            // print uri
            System.err.println(arg+':');
   
            JFrame frame = null;
            if (parserNameIndex == argv.length-1) {
                // null behaviour is blank screen - eg no JTree, or file dispalyed
                frame = new TreeView("");
            } else {
                frame = new TreeView(arg);
            }
            frame.addWindowListener(new java.awt.event.WindowAdapter() {
             public void windowClosing(java.awt.event.WindowEvent e) {
                 System.exit(0);
             }
            });
            frame.setSize(790, 590);
            frame.setVisible(true);
        }
    } // main(String[])

    /** Prints the usage. */
    private static void printUsage() {

        System.err.println("usage: java ui.TreeViewer (options) uri ...");
        System.err.println();
        System.err.println("options:");
        System.err.println("  -p name  Specify DOM parser class by name.");
        System.err.println("           Default parser: "+DEFAULT_PARSER_NAME);
        System.err.println("  -h       This help screen.");

    } // printUsage()

} // class TreeViewer
