Displaying the DOM

As described in Chapter 3, "The Document Object Model (DOM)," the DOM represents an XML document as a hierarchical data structure or tree. The Java Platform Standard Edition (SE) includes support for a graphical tree view in the Swing package (javax.swing ) called a JTree . The JTree component renders a tree model. Our job will be to encapsulate the DOM data model inside a Swing tree model so that it can be rendered as a JTree . The result of this is presented in Figure 7.1.

Figure 7.1. A DOM rendered with a JTree.


The DOM represents an XML document in two ways—a flat representation in which every member of the tree is a node and a hierarchical (or inheritance-based) representation in which tree members are specific subclasses like element, text node or attribute. I will use the flat representation because a JTree view is not concerned with the type of node, just its position in the tree and rendering.

Swing Component Basics

For those not experienced with building graphical user interfaces in Swing, the following are the prerequisite Swing concepts relevant to this chapter:

  • Swing components are pure Java components called lightweight components. The only exceptions to this are Swing's top-level containers: frames, applets, windows, and dialogs. Swing's UI components all begin with the letter J: JFrame, JLabel, and JMenu. The rest of the numerous classes in the Swing packages are supporting classes.

  • Swing components are not simple and are composed of multiple cooperating objects. The Swing components extend java.awt.Container. They support a cross-platform look-and-feel, pluggable look-and-feels (including native), and multiple views of a single data model.

  • For backward compatibility with the AWT, the components in the AWT all have Swing counterparts with nearly identical APIs. The one significant exception is the JFrame, in which you add components to the contentPane instead of to the JFrame.

  • The Swing component architecture is based on the Model-View-Controller paradigm. This is the same architecture used by Smalltalk for managing multiple look-and-feels. This architecture separates components into three types of objects: models, views, and controllers.

    Models are responsible for maintaining data and notifying views when the data changes.

    Views provide a visual representation of the models data and update themselves when the model changes.

    Controllers modify the model. For graphical user interface components, the most common way to do this is via handling events from the view (mouse clicks, key strokes, and so on). The Swing MVC implementation combines the view and controller into a UI delegate.

These concepts are demonstrated in the sample applications that follow.

The DomViewer Application

There are several options for converting a DOM tree into a tree suitable for rendering in a JTree. The requirements for a JTree model are defined by the interface TreeModel (see Listing 7.1).

Code Listing 7.1. TreeModel Interface
package javax.swing.tree;
import javax.swing.event.*;
public interface TreeModel
{
public Object getRoot();
public Object getChild(Object parent, int index);
public int getChildCount(Object parent);
public boolean isLeaf(Object node);
public void valueForPathChanged(TreePath path, Object newValue);
public int getIndexOfChild(Object parent, Object child);
void addTreeModelListener(TreeModelListener l);
void removeTreeModelListener(TreeModelListener l);
}

The requirements for a TreeModel are simple for any tree data structure to implement; however, your implementation of this interface must be consistent. For example, the Object returned via the getChild() method must be able to determine if it is a leaf node when passed into the isLeaf() method. There are two options for creating the necessary TreeModel: wrap a DOM in a new class that translates TreeModel method calls into DOM method calls or use the DefaultMutableTreeNode class to create a new tree by extracting the values from the DOM tree.

While creating a DefaultMutableTreeNode tree requires less understanding of the workings of Jtree, wrapping the DOM is the better method because a DOM is already a tree. Therefore it is wasteful to create a second tree that mirrors the DOM tree. The better solution is to make the DOM's tree model conform to the JTree's TreeModel interface. The DomTreeModel inner class in Listing 7.2 implements the TreeModel by wrapping the DOM. The implementation boils down to returning the correct DOM child node, returning the number of children for a node, and other housekeeping operations.

The following are the salient features of the DomViewer application presented in Listing 7.2:

  • The user interface sports a frame, a menu bar with one menu (File), and two options (Save and Exit), and the frame is filled with a graphical representation of the DOM tree.

  • On startup, you enter the XML file to render and an optional -default argument that chooses between default rendering and custom rendering. If you do not specify the -default, the program uses a custom TreeCellRenderer and a custom TreeCellEditor.

  • With the custom renderer and editor, the leaf nodes of the DOM tree will be available to be edited using a JtextField. After nodes are edited and the user hits return, the new value is stored in the DOM. The Save option in the File menu will allow you to write the new XML file to disk.

Code Listing 7.2. DomViewer Application
/* DomViewer.java */
package sams.chp7;

import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
import com.sun.xml.tree.XmlDocument;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.w3c.dom.*;

public class DomViewer extends JFrame implements ActionListener
{
    XmlDocument doc;

    public Insets getInsets()
    {
        return new Insets(25,5,5,5);
    }

    class DomTreeModel implements TreeModel
    {
        Document doc;
        ArrayList listeners = new ArrayList();

        public DomTreeModel(Document doc)
        {
            this.doc = doc;
        }

        // Tree model methods
        public Object getRoot()
        {
            return doc.getDocumentElement();
        }

        public Object getChild(Object parent, int i)
        {
            Object o = null;
            NodeList children = ((Node)parent).getChildNodes();
            return children.item(i);
        }

        public int getChildCount(Object parent)
        {
            NodeList children = ((Node)parent).getChildNodes();
            return children.getLength();
        }

        public int getIndexOfChild(Object parent, Object child)
        {
            NodeList children = ((Node)parent).getChildNodes();
            int size = children.getLength();
            int i = -1;
            for(i = 0; i<size; i++)
            {
                Node n = children.item(i);
                if (n == child)
                    break;
            }
            return i;
        }

        public boolean isLeaf(Object node)
        {
            boolean hasChildren = ((Node)node).hasChildNodes();
            return !hasChildren;
        }

        public void valueForPathChanged(TreePath path, Object newValue)
        {
            Node n = (Node) path.getLastPathComponent();
            n.setNodeValue((String)newValue);
        }

        public void addTreeModelListener(TreeModelListener l)
        {
            listeners.add(l);
        }

        public void removeTreeModelListener(TreeModelListener l)
        {
            listeners.remove(l);
        }
    }
    
    class DomTreeCellRenderer implements TreeCellRenderer
    {
        public Component getTreeCellRendererComponent(JTree tree,
                                                      Object value,
                                                      boolean selected,
                                                      boolean expanded,
                                                      boolean leaf,
                                                      int row,
                                                      boolean hasFocus)
        {
            Component c = null;
            Color highlight = new Color(102,255,255);

            //find and see if the node is element node
            if (!leaf)
            {
                JLabel l = new JLabel();
                l.setOpaque(true);
                c = l;
                if ( selected )
                {
                    l.setBackground(highlight);
                    l.setForeground(Color.black);
                    l.setText(((Node)value).getNodeName());
                }
                else
                {
                    l.setBackground(Color.white);
                    l.setForeground(new Color(0,51,204));
                    l.setText(((Node)value).getNodeName());
                }
            }
            else  // a leaf node
            {
                JTextField f = new JTextField(30);
                //f.setEditable(true);
                c = f;
                if ( selected )
                {
                    f.setBackground(new Color(195, 195, 250));
                    f.setForeground(Color.black);
                    f.setText(((Node)value).getNodeValue());
                }
                else
                {
                    f.setBackground(Color.white);
                    f.setForeground(Color.black);
                    f.setText(((Node)value).getNodeValue());
                }
            }

            return c;
        }
    }

    class DomTreeCellEditor  extends DefaultCellEditor
                             implements TreeCellEditor
    {
        private JTree tree;

        public DomTreeCellEditor(JTree tree)
        {
            super(new JTextField(30));
            this.tree = tree;
        }

        public boolean canEditImmediately(EventObject e)
        {
            return true;
        }

        public boolean isCellEditable(EventObject e)
        {
            boolean result = false;

            if (e instanceof MouseEvent)
            {
                MouseEvent me = (MouseEvent) e;
                TreePath path = tree.getPathForLocation(me.getX(), me.getY());
                if (path != null)
                {
                    Node n = (Node) path.getLastPathComponent();
                    if (n != null && !n.hasChildNodes())
                        result = true;
                }
            }

            return result;
        }
        public Component getTreeCellEditorComponent(JTree tree,
                                                    Object value,
                                                    boolean selected,
                                                    boolean expanded,
                                                    boolean leaf,
                                                    int row)
        {
            JTextField f = (JTextField) getComponent();
            Color highlight = new Color(102,255,255);
            if (leaf)
            {
                if ( selected )
                {
                    f.setBackground(highlight);
                    f.setForeground(Color.black);
                    f.setText(((Node)value).getNodeValue());
                }
                else
                {
                    f.setBackground(Color.white);
                    f.setForeground(Color.black);
                    f.setText(((Node)value).getNodeValue());
                }
            }
            else
                return null;

            return f;
        }
    }
    public DomViewer(String fileName, boolean defaultRenderer) throws Exception
    {
        super(fileName);

        // add a simple menu
        JMenuBar bar = new JMenuBar();
        setJMenuBar(bar);
        JMenu fileMenu = new JMenu("File");
        JMenuItem saveItem = new JMenuItem("Save");
        saveItem.addActionListener(this);
        JMenuItem quitItem = new JMenuItem("Quit");
        quitItem.addActionListener(this);
        fileMenu.add(saveItem);
        fileMenu.add(quitItem);
        bar.add(fileMenu);
        // create a SAX InputSource
        InputSource is = new
                    InputSource(new File(fileName).toURL().toString());

        // create a DOM Document
        doc = XmlDocument.createXmlDocument(is, true);
        DomUtil.normalizeDocument(doc.getDocumentElement());

        // Create a Tree Model from the Document
        DomTreeModel model = new DomTreeModel(doc);

        // Create a renderer
        DomTreeCellRenderer renderer = new DomTreeCellRenderer();

        // Create the JTree
        JTree tree = new JTree(model);
        tree.putClientProperty("JTree.lineStyle", "Angled");

        // Create an editor
        DomTreeCellEditor editor = new DomTreeCellEditor(tree);

        if (!defaultRenderer)
        {
            tree.setCellRenderer(renderer);
            tree.setEditable(true);       
            tree.setCellEditor(editor);
        }
        
        // Create a scroll pane
        JScrollPane scroll = new JScrollPane(tree);

        getContentPane().add("Center", scroll);

        addWindowListener(new WindowAdapter()
                          {
                            public void windowClosing(WindowEvent we)
                            { System.exit(1); }
                          });

        setLocation(100,100);
        setSize(600,400);
        setVisible(true);
    }

    public void actionPerformed(ActionEvent evt)
    {
        String command = evt.getActionCommand();
        if (command.equalsIgnoreCase("Quit"))
        {
            System.exit(0);
        }
        else if (command.equalsIgnoreCase("Save"))
        {
            JFileChooser chooser = new
                         JFileChooser(System.getProperty("user.dir"));
            int stat = chooser.showSaveDialog(this);
            if (stat == JFileChooser.APPROVE_OPTION)
            {
                File f = chooser.getSelectedFile();
                try
                {
                    FileWriter fw = new FileWriter(f);
                    doc.write(fw);
                    fw.close();
                } catch (IOException ioe)
                  {
                    System.out.println("Error writing to: " + f);
                  }
            }
        }
    }

    public static void main(String args[])
    {
        if (args.length < 1)
        {
            System.out.println("USAGE: java sams.chp7.DomViewer xmlfile [-default]");
            System.exit(1);
        }

        try
        {
            boolean defaultRendering = false;
            if (args.length == 2 && args[1].equals("-default"))
                defaultRendering = true;

            new DomViewer(args[0], defaultRendering);
        } catch (Throwable t)
          {
            t.printStackTrace(); 
          }
    }
}

When Listing 7.2 is run without the -default option, it produces Figure 7.1. When run with the -default option, it produces Figure 7.2.

Figure 7.2. A DOM with default JTree rendering.


Note the following key points about the source code in Listing 7.2:

  • The main() method merely instantiates a DomViewer object and passes in the arguments from the command line (filename and optionally a -default argument).

  • The DomViewer constructor parses the XML document using the Sun Microsystems parser which produces an XmlDocument object that implements the DOM Document interface. This XmlDocument is passed into the constructor for the DomTreeModel object. The DomTreeModel is the most important object in this program.

  • The DomTreeModel is an implementation of the TreeModel interface as a pass-through to the DOM API. Each requirement for a JTree TreeModel is translated into the corresponding DOM API call for the DOM Document object that is passed in to the constructor. All of the methods in this interface are straightforward implementations to satisfy the requirements of the interface method. For example, getChild(parent, i) requests the ith child of the parent object that was passed in. Because we know that all children we return will be DOM nodes, we can cast the incoming parent object to a DOM node and then get the NodeList of child nodes (using the getChildNodes() method) and from that object, the ith child. Note: For the default renderer, returning a node object will render a JLabel with whatever the node's toString() method returns. Because this is not always what we want, a custom TreeCellRenderer object is needed.

  • The DomTreeCellRenderer object has only one method: getTreeCellRendererComponent(). The default Renderer returns a JLabel. The policy this renderer follows is that it returns a JLabel for non-leaf nodes and a JTextField for leaf nodes. In addition, this method uses the nodeName for non-leaf nodes and the nodeValue for leaf nodes. The reason you cannot always use a node value is because there are certain cases (such as an element) in which the node value is null. Using a JTextField for the leaf nodes allows us to edit the values in those nodes. To allow editing of these nodes, a TreeCellEditor was necessary.

  • The key responsibilities of the DomTreeCellEditor are to allow editing to take place and return the component to be used as the editor.

  • The last method to cover in the DomViewer application is the actionPerformed() method. This is the only method in the ActionListener interface, and is called in response to a menu selection. The Save command makes use of the JFileChooser component to display the Save As dialog box. After a valid file is returned, the write() method of the XmlDocument object is used to write the DOM to a text file.

Since the DOM will have text nodes of just whitespace, we need to normalize the tree to strip out these blank text nodes. To do that we use the DomUtil class and the normalizeDocument() method in Listing 7.3:

Code Listing 7.3. DomUtil.java
package sams.chp7;
import java.util.*;
import org.w3c.dom.*;

public class DomUtil
{
    public static void normalizeDocument(Node n)
    {
        if (!n.hasChildNodes())
            return;

        NodeList nl = n.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++)
        {
            Node cn = nl.item(i);
            if (cn.getNodeType() == Node.TEXT_NODE &&
                isBlank(cn.getNodeValue()))
            {
                n.removeChild(cn);
                i--;
            }
            else
                normalizeDocument(cn);
        }
    }

    public static boolean isBlank(String buf)
    {
        if (buf == null)
            return false;

        int len = buf.length();
        for (int i=0; i < len; i++)
        {
            char c = buf.charAt(i);
            if (!Character.isWhitespace(c))
                return false;
        }

        return true;
    }
}

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.146.152.99