blob: c790dd13647d4665696913bf8f2f7fe06a47c80c [file] [log] [blame]
/*
* 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