/*
 * The Apache Software License, Version 1.1
 *
 *
 * Copyright (c) 1999,2000 The Apache Software Foundation.  All rights 
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. 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.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:  
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Xerces" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written 
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR
 * ITS 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.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation and was
 * originally based on software copyright (c) 1999, International
 * Business Machines, Inc., http://www.apache.org.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */
package dom.traversal;

import java.awt.*;
import java.awt.event.*;
import java.util.Hashtable;
import java.util.Enumeration;
import javax.swing.*;
import javax.swing.tree.*;
import javax.swing.event.*;
import org.apache.xerces.parsers.*;
import org.w3c.dom.*;
import org.w3c.dom.traversal.*;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import ui.DOMTreeFull;

/** This class shows a DOM Document in a JTree, and presents controls
 *  which allow the user to create and view the progress of a NodeIterator
 *  in the DOM tree.
 */
public class IteratorView 
    extends JFrame 
    implements ActionListener {

    Document document;
    TreeNode lastSelected;
    DOMParser parser;
    JTextArea messageText;
    JScrollPane messageScroll;
    DOMTreeFull jtree;
    NodeIterator iterator;
    NameNodeFilter nameNodeFilter;
    
    JButton nextButton;
    JButton prevButton;
    JButton removeButton;
    JButton addButton;
    JTextField addText;
    JButton newIterator;
    JList whatToShow;
    JCheckBox match;
    JTextField nameFilter;
    String whatArray[] = new String [] { 
            "ALL",
            "ELEMENT",
            "ATTRIBUTE",
            "TEXT",
            "CDATA_SECTION",
            "ENTITY_REFERENCE",
            "ENTITY",
            "PROCESSING_INSTRUCTION", 
            "COMMENT", 
            "DOCUMENT", 
            "DOCUMENT_TYPE",
            "DOCUMENT_FRAGMENT",
            "NOTATION" 
            };
    JCheckBox expandERs;
    
    Hashtable treeNodeMap = new Hashtable();
    
    
    /** main */
    public static void main (String args[]) {

        if (args.length > 0) {
            String filename = args[0];
            try {
                IteratorView frame = new IteratorView(filename) ;
                frame.addWindowListener(new java.awt.event.WindowAdapter() {
                 public void windowClosing(java.awt.event.WindowEvent e) {
                     System.exit(0);
                 }
                });
                frame.setSize(640, 480);
                frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace(System.err);
                }
        }
    }
    
    /** Constructor */
    public IteratorView (String filename) {
        super("IteratorView: "+filename);
        try {
            parser = new DOMParser();
            parser.setFeature("http://apache.org/xml/features/dom/defer-node-expansion", true);
            parser.setFeature("http://apache.org/xml/features/continue-after-fatal-error", true);
            Errors errors = new Errors();
            parser.setErrorHandler(errors);
            parser.parse(filename);
            document = parser.getDocument();
            
            if (!document.isSupported("Traversal", "2.0")) {
                // This cannot happen with ou DOMParser...
                throw new RuntimeException("This DOM Document does not support Traversal");
            }

            // jtree  UI setup
            jtree = new DOMTreeFull((Node)document);
            jtree.getSelectionModel().setSelectionMode
                (TreeSelectionModel.SINGLE_TREE_SELECTION);
            jtree.setRootVisible(false);

            // Listen for when the selection changes, call nodeSelected(node)
            jtree.addTreeSelectionListener(
                new TreeSelectionListener() {
                    public void valueChanged(TreeSelectionEvent e) {
                        TreePath path = (TreePath)e.getPath(); 
                        TreeNode treeNode = (TreeNode)path.getLastPathComponent();
                        if(jtree.getSelectionModel().isPathSelected(path))
                                nodeSelected(treeNode);
                    }
                }
            );
            
            
            //expandTree();
            
                        
            // controls
            
            //iterate panel
            JPanel iteratePanel = new JPanel();
            iteratePanel.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createTitledBorder("Iterate"),
                BorderFactory.createEmptyBorder(4, 4, 4, 4)
                ));
            
            prevButton = new JButton("Previous");
            iteratePanel.add(prevButton);
            prevButton.addActionListener(this);
            
            nextButton = new JButton("Next");
            iteratePanel.add(nextButton);
            nextButton.addActionListener(this);
        
            //DOM tree panel
            JPanel domPanel = new JPanel();
            domPanel.setLayout(new BorderLayout());
            domPanel.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createTitledBorder("Selected Node"),
                BorderFactory.createEmptyBorder(4, 4, 4, 4)
                ));
            
            removeButton = new JButton("Remove Selected Node");
            domPanel.add(removeButton, BorderLayout.NORTH);
            removeButton.addActionListener(this);
            
            addButton = new JButton("Add Text Node");
            addText = new JTextField(10);
            domPanel.add(addButton, BorderLayout.CENTER);
            domPanel.add(addText, BorderLayout.SOUTH);
            addButton.addActionListener(this);
         
            // iterator settings
            JPanel settingsPanel = new JPanel();
            settingsPanel.setLayout(new BorderLayout());
            settingsPanel.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createTitledBorder("Iterator Settings"),
                BorderFactory.createEmptyBorder(4, 4, 4, 4)
                ));
            JPanel iteratorPanel = new JPanel();
            newIterator = new JButton("createNodeIterator");
            expandERs = new JCheckBox("expandEntityReferences");
            iteratorPanel.add(newIterator);
            expandERs.setSelected(true);
            iteratorPanel.add(expandERs);
            settingsPanel.add(iteratorPanel, BorderLayout.NORTH);
            newIterator.addActionListener(this);
    
            JPanel whatPanel = new JPanel();
            whatPanel.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createTitledBorder("whatToShow"),
                BorderFactory.createEmptyBorder(4, 4, 4, 4)
                ));
            whatToShow = new JList(DOMTreeFull.whatArray);
            JScrollPane whatScroll = 
            new JScrollPane(whatToShow) {
                    public Dimension getPreferredSize(){
                        return new Dimension(200, 75 );
                    }
                };
            
            whatPanel.add(whatScroll);
            
            
            JPanel filterPanel = new JPanel();
            filterPanel.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createTitledBorder("NodeNameFilter"),
                BorderFactory.createEmptyBorder(4, 4, 4, 4)
                ));
            filterPanel.setLayout(new BorderLayout());
            match = new JCheckBox("match/ignore node name", true);
            nameFilter = new JTextField(10);
            filterPanel.add(match, BorderLayout.NORTH);
            filterPanel.add(nameFilter, BorderLayout.SOUTH);
            
            settingsPanel.add(whatPanel, BorderLayout.WEST);
            settingsPanel.add(filterPanel, BorderLayout.EAST);
            

            // Listen for when the selection changes, call nodeSelected(node)
            whatToShow.addListSelectionListener(
                new ListSelectionListener() {
                    public void valueChanged(ListSelectionEvent e) {
                        // do nothing on selection...
                    }
                }
            );
            
            
            JPanel controlsPanel = new JPanel(new BorderLayout());
            JPanel buttonsPanel = new JPanel(new BorderLayout());
            buttonsPanel.add(iteratePanel, BorderLayout.NORTH);
            buttonsPanel.add(domPanel, BorderLayout.SOUTH);
            controlsPanel.add(buttonsPanel, BorderLayout.WEST);
            controlsPanel.add(settingsPanel, BorderLayout.CENTER);
            
            controlsPanel.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createTitledBorder("Controls"),
                BorderFactory.createEmptyBorder(4, 4, 4, 4)
                ));
                
                
            // tree panel    
            JPanel treePanel = new JPanel(new BorderLayout());
                
            JScrollPane treeScroll = new JScrollPane(jtree) ;
            treeScroll.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createTitledBorder("Tree View"),
                BorderFactory.createEmptyBorder(4, 4, 4, 4)
                ));

            // message text UI setup
            messageText = new JTextArea(3,5);

            JPanel messagePanel = new JPanel(new BorderLayout());
            messageScroll = new JScrollPane(messageText);
            messagePanel.add(messageScroll);
            messagePanel.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createTitledBorder("Messages"),
                BorderFactory.createEmptyBorder(4, 4, 4, 4)
                ));
            
            JPanel mainPanel = new JPanel();
            mainPanel.setLayout(new BorderLayout());
            mainPanel.add(controlsPanel, BorderLayout.NORTH);
            mainPanel.add(treeScroll, BorderLayout.CENTER);
            mainPanel.add(messagePanel, BorderLayout.SOUTH);
            getContentPane().add(mainPanel);
            
            Hashtable errorNodes = errors.getErrorNodes();
            Enumeration elements = errorNodes.elements();
            while (elements.hasMoreElements()) {
                //*** append errors to messageText
                messageText.append( (String)elements.nextElement());
            }    
            
            // This cast must work, because we have tested above
            // with document.isSupported("Traversal")
            iterator = ((DocumentTraversal)document).
                createNodeIterator(
                    document, 
                    NodeFilter.SHOW_ALL, 
                    new NameNodeFilter(),
                    true);
            
        } catch (Exception e) {
            e.printStackTrace(System.err);
        }
    }
    
    public void actionPerformed(ActionEvent e) {
        
        if (e.getSource() == newIterator) {
            Node node = document;
            
            // whatToShow section
            int  [] indices = whatToShow.getSelectedIndices();
            int mask = 0x0;
            for (int i = 0; i < indices.length; i++) {
                if (indices[i] == 0) {
                    mask = 0xFFFF;
                    break;
                } else {
                    mask = mask | 1<<indices[i]-1;
                }
            }
            // filter section
            String nameText = nameFilter.getText();
            boolean matched = match.isSelected();
            if (nameNodeFilter==null) {
                nameNodeFilter = new NameNodeFilter();
            }
            if (nameText.equals("")) {
                setMessage("NodeNameFilter name is \"\". Assuming null.");
                nameText = null;
            }
            nameNodeFilter.setName(nameText);
            nameNodeFilter.setMatch(matched);
            
            if (iterator !=null) iterator.detach();
            boolean expand = expandERs.isSelected();
            iterator = ((DocumentTraversal)document).
                createNodeIterator(
                    node, 
                    (short)mask, 
                    nameNodeFilter,
                    expand);
            setMessage("doc.createNodeIterator("+ 
                        " root="+node+
                        ", whatToShow="+mask+
                        ", match="+matched+
                        ", name="+nameText+")"
                        );
            return;
            
        } 
        
        if (e.getSource() == addButton) {
            
            String text = addText.getText();
            
            if (text==null) return;
            
            TreeNode treeNode = (TreeNode)jtree.getLastSelectedPathComponent();
            if (treeNode == null) {
                messageText.append("Must select a tree component to add a child to it.");
                return;
            }
            TreePath path = new TreePath(
                    ((DefaultTreeModel)jtree.getModel()).getPathToRoot(treeNode));
            if (path == null) {
                setMessage("Could not create a path.");
                return;
            }
            if(!jtree.getSelectionModel().isPathSelected(path))
                return;
            Node node = jtree.getNode(treeNode);
            Node textNode = document.createTextNode(text);
            try {
                node.appendChild(textNode);
            } catch (DOMException dome) {
                setMessage("DOMException:"+dome.code+", "+dome);
                return;
            }
            ((DOMTreeFull.Model)jtree.getModel()).insertNode(textNode, (MutableTreeNode)treeNode);
            
            return;
        }
        if (e.getSource() == nextButton) {          
            Node node = iterator.nextNode();
            if (node==null) {
                setMessage("iterator.nextNode() == null");
                return;
            } 
            
            setMessage("iterator.nextNode() == "+node);

            TreeNode treeNode = jtree.getTreeNode(node);
            if (treeNode == null) {
                setMessage("No JTree TreeNode for Node name:" + node.getNodeName());
                return;
            }
            
            TreePath path = new TreePath(
                    ((DefaultTreeModel)jtree.getModel()).getPathToRoot(treeNode));
            jtree.requestFocus();
            jtree.setSelectionPath(path);
            jtree.scrollPathToVisible(path);
            return;
        } 
        
        if (e.getSource() == prevButton) {          
            Node node = iterator.previousNode();
            if (node==null) {
                setMessage("iterator.previousNode() == null");
                return;
            } 
            
            setMessage("iterator.previousNode() == "+node);

            TreeNode treeNode = jtree.getTreeNode(node);
            if (treeNode == null) {
                setMessage("No JTree TreeNode for Node name:" + node.getNodeName());
                return;
            }
            
            TreePath path = new TreePath(
                    ((DefaultTreeModel)jtree.getModel()).getPathToRoot(treeNode));
            jtree.requestFocus();
            jtree.setSelectionPath(path);
            jtree.scrollPathToVisible(path);
            return;
        }
        if (e.getSource() == removeButton) {
            
            /** If the node is not selected don't remove. */
            TreeNode treeNode = (TreeNode)jtree.getLastSelectedPathComponent();
            if (treeNode == null) {
                messageText.append("Must select a tree component to remove it.");
                return;
            }
            TreePath path = new TreePath(
                    ((DefaultTreeModel)jtree.getModel()).getPathToRoot(treeNode));
            if (path == null) {
                setMessage("Could not create a path.");
                return;
            }
            if(!jtree.getSelectionModel().isPathSelected(path))
                return;
            Node node = jtree.getNode(treeNode);
            if (node == null) return;
            Node parent = node.getParentNode();
            if (parent == null) return;
            
            parent.removeChild(node);
    
            ((DefaultTreeModel)jtree.getModel()).removeNodeFromParent((MutableTreeNode)treeNode);
            return;
        } 
        
    }
    
    /** Helper function to set messages */
    void setMessage(String string) {
        messageText.selectAll();
        messageText.cut();
        messageText.append(string);
        messageText.setCaretPosition(0);
    }
        
    /** called when our JTree's nodes are selected.
     */
    void nodeSelected(TreeNode treeNode) {

        lastSelected = treeNode;
        Node node = jtree.getNode(treeNode);
        
        System.out.println("nodeSelected.node="+node);
        if (node == null) return;
                            
        setMessage(DOMTreeFull.toString(node));
    }
    
    /** Utility function to expand the jtree */
    void expandTree() {
        for (int i = 0; i < jtree.getRowCount(); i++) {
            jtree.expandRow(i);
        }
    }
    
    class Errors implements ErrorHandler {

        Hashtable errorNodes = new Hashtable();

        public void warning(SAXParseException ex) {
            store(ex, "[Warning]");
        }

        public void error(SAXParseException ex) {
            store(ex, "[Error]");
        }

        public void fatalError(SAXParseException ex) throws SAXException {
            store(ex, "[Fatal Error]");
        }

        public Hashtable getErrorNodes() {
            return errorNodes;
        }

        public Object getError(Node node) {
            return errorNodes.get(node);
        }

        public void clearErrors() {
            errorNodes.clear();
        }

        void store(SAXParseException ex, String type) {

            // build error text
            String errorString= type+" at line number, "+ex.getLineNumber()
                +": "+ex.getMessage()+"\n";
            Node currentNode = null;
            try {
                currentNode = (Node)parser.getProperty("http://apache.org/xml/properties/dom-node");
            } catch (SAXException se) {
                System.err.println(se.getMessage());
                return;
            }
            if (currentNode == null) return;

            // accumulate any multiple errors per node in the Hashtable.
            String previous = (String) errorNodes.get(currentNode);
            if (previous != null)
                errorNodes.put(currentNode, previous +errorString);
            else
                errorNodes.put(currentNode, errorString);
        }
    }
    
}
