/* * 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$ */ 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