/*
* Licensed Materials - Property of Perforce Software, Inc.
* © Copyright Perforce Software, Inc. 2014, 2021
* © Copyright IBM Corp. 2009, 2014
* © Copyright ILOG 1996, 2009
* All Rights Reserved.
*
* Note to U.S. Government Users Restricted Rights:
* The Software and Documentation were developed at private expense and
* are "Commercial Items" as that term is defined at 48 CFR 2.101,
* consisting of "Commercial Computer Software" and
* "Commercial Computer Software Documentation", as such terms are
* used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4,
* as applicable.
*/
package xml;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.URL;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.border.TitledBorder;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellEditor;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import ilog.views.chart.IlvChart;
import ilog.views.chart.IlvChartLayout;
import ilog.views.chart.IlvChartRenderer;
import ilog.views.chart.IlvLegend;
import ilog.views.chart.data.IlvDataSet;
import ilog.views.chart.data.xml.IlvXMLDataReader;
import ilog.views.chart.data.xml.IlvXMLDataSource;
import ilog.views.chart.graphic.IlvMarkerFactory;
import ilog.views.chart.renderer.IlvPolylineChartRenderer;
import ilog.views.chart.renderer.IlvSinglePolylineRenderer;
import ilog.views.util.IlvImageUtil;
import ilog.views.util.xml.XMLUtil;
import shared.AbstractChartExample;
/**
* An example that shows how to import data contained in an XML document. The
* document is displayed in three different graphic representations:
* <ul>
* <li>As a chart of polylines</li>
* <li>As a tree</li>
* <li>As text</li>
* </ul>
*/
public class XMLDemo extends AbstractChartExample {
/** The name of the default file. */
static final String DEFAULT_FILE = "data.xml";
private ImageIcon attrIcon;
private ImageIcon textIcon;
/** The chart data source containing the data. */
IlvXMLDataSource xmlDS;
/** The data reader. */
IlvXMLDataReader xmlReader;
/** The TreeModel used to visualize the DOM in a tree. */
DefaultTreeModel treeModel;
/** The TextArea used to visualize the document as text. */
JTextArea textArea;
/** The document ID. */
String documentId;
/** The document currently being viewed in the tree. */
Document document;
private boolean internalUpdate = false;
/**
* A <code>TreeModelListener</code> used to track any modification performed
* on the tree DOM.
*/
TreeModelListener treeModelListener = new TreeModelListener() {
Override
public void treeNodesChanged(TreeModelEvent evt) {
handleTreeModelEvent(evt);
}
Override
public void treeNodesInserted(TreeModelEvent evt) {
treeNodesChanged(evt);
}
Override
public void treeNodesRemoved(TreeModelEvent evt) {
treeNodesChanged(evt);
}
Override
public void treeStructureChanged(TreeModelEvent evt) {
treeNodesChanged(evt);
}
};
/**
* Initializes a new <code>XMLSample</code>.
*/
Override
public void init(Container container) {
super.init(container);
container.setLayout(new GridLayout(2, 1));
try {
attrIcon = new ImageIcon(IlvImageUtil.loadImageFromFile(getClass(), "attr.gif"));
textIcon = new ImageIcon(IlvImageUtil.loadImageFromFile(getClass(), "text.gif"));
} catch (Exception e) {
System.err.println("Cannot load images. " + e.getMessage());
e.printStackTrace();
}
// The absolute path of the default file is memorized to be able to
// set a systemId when a new InputSource is created from a stream.
URL documentURL = getClass().getResource(DEFAULT_FILE);
documentId = (documentURL == null) ? null : documentURL.toString();
// ======================= Chart initialization =======================
IlvChart chart = new IlvChart();
chart.setAntiAliasing(true);
// The chart data source. An IlvXMLDataSource is a specialized data source
// used in association with an IlvXMLDataReader to load data from an
// XML document. The document must be conform to the Perforce JViews
// Charts DTD
// (more information in the Reference Manual).
xmlDS = new IlvXMLDataSource();
xmlReader = new IlvXMLDataReader();
xmlReader.setValidating(true);
// Load the data contained in the data.xml file in the data source.
loadDefaultFile();
// Each data set contained in the XML file is rendered as a polyline.
IlvPolylineChartRenderer renderer = new IlvPolylineChartRenderer() {
Override
protected IlvChartRenderer createChild(IlvDataSet dataSet) {
IlvSinglePolylineRenderer child = new IlvSinglePolylineRenderer();
child.setMarker(IlvMarkerFactory.getSquareMarker());
return child;
}
};
renderer.setDataSource(xmlDS);
chart.addRenderer(renderer);
// Add a legend to the chart on top of the chart area.
IlvLegend legend = new IlvLegend();
chart.addLegend(legend, IlvChartLayout.NORTH_BOTTOM);
// ======================== UI Initialization =========================
JPanel panel = new JPanel(new GridLayout(1, 2));
JPanel left = new JPanel(new BorderLayout());
// The text area is used to display the XML document as text.
textArea = new JTextArea();
try {
BufferedReader in = new BufferedReader(new InputStreamReader(((new URL(documentId)).openStream())));
textArea.read(in, null);
in.close();
} catch (IOException e) {
e.printStackTrace();
}
left.add(new JScrollPane(textArea), BorderLayout.CENTER);
JButton button = new JButton("Send to chart");
button.addActionListener(new ActionListener() {
Override
public void actionPerformed(ActionEvent evt) {
sendToChart();
}
});
left.add(button, BorderLayout.SOUTH);
left.setBorder(new TitledBorder(null, "Text view (Edit text + \"Send\" to update)", TitledBorder.CENTER,
TitledBorder.DEFAULT_POSITION));
// The JTree is used to display the DOM as a tree.
treeModel = new DefaultTreeModel(new DefaultMutableTreeNode("<No document>"));
Document document = xmlReader.getDocument();
fillTreeModel(document);
treeModel.addTreeModelListener(treeModelListener);
JTree tree = new JTree(treeModel);
configureTree(tree);
panel.add(left);
JPanel right = new JPanel(new BorderLayout());
right.setBorder(new TitledBorder(null, "Tree view (Click on a node to edit)", TitledBorder.CENTER,
TitledBorder.DEFAULT_POSITION));
right.add(new JScrollPane(tree), BorderLayout.CENTER);
panel.add(right);
// A button to reload the original document.
button = new JButton("Reset to default data");
button.addActionListener(new ActionListener() {
Override
public void actionPerformed(ActionEvent evt) {
loadDefaultFile();
fillTreeModel(xmlReader.getDocument());
}
});
// Add the button to the chart as its footer. The footer is a JComponent
// that can be added at the bottom of the chart area.
chart.setFooter(button);
// The header is a JComponent that can be added on the top of the chart.
// As a convenience, the setHeaderText method offers an easy way to
// have just a label as header: a JLabel initialized with the specified
// string is automatically created and added to the chart.
chart.setHeaderText("Chart view");
container.add(chart);
container.add(panel);
}
/**
* Loads the document edited in the text area in the chart. This method also
* refreshes the tree view.
*/
protected void sendToChart() {
// The contents of the text area is first saved in a StringWriter. Then
// an InputSource that will read the writer buffer is created. Finally,
// The InputSource is read by the data source reader to initialize the
// data sets.
Writer writer = new StringWriter();
try {
textArea.write(writer);
InputSource src = new InputSource(new StringReader(writer.toString()));
src.setSystemId(documentId);
xmlDS.load(src, xmlReader);
// Disable the internal TreeModel listener.
internalUpdate = true;
// Refresh the tree according to the new document that has just been
// read.
fillTreeModel(xmlReader.getDocument());
internalUpdate = false;
} catch (Exception e) {
JOptionPane.showMessageDialog(null, "Not a valid JViews Charts XML document.", "Cannot load data !",
JOptionPane.ERROR_MESSAGE);
}
}
/**
* Load the contents of the default <i>data.xml</i> file.
*/
protected void loadDefaultFile() {
try {
// Load the chart data contained in the given document. This method
// parses the XML document (calling the IlvXMLDataReader.read() method)
// and initializes its data sets.
xmlDS.load(new InputSource(documentId), xmlReader);
xmlReader.getDocument().getDoctype();
} catch (Exception e) {
e.printStackTrace();
}
}
// ==================== JTree handling ===============================
/**
* Returns a string representation of the current DOM viewed in the tree. May
* return <code>null</code> if the transformation failed.
*/
private String documentToString() {
String doc = null;
try {
ByteArrayOutputStream writer = new ByteArrayOutputStream();
XMLUtil.WriteDocument(document, writer, document.getDoctype().getPublicId(), document.getDoctype().getSystemId(),
null);
doc = writer.toString();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
// System.err.println("doc : \n" + doc);
return doc;
}
/**
* Called when an event occurs on the <code>TreeModel</code>. This methods
* loads the new DOM in the chart and text views.
*/
protected void handleTreeModelEvent(TreeModelEvent evt) {
if (internalUpdate)
return;
// The new DOM is saved as a string that is used to create a reader
// needed to initialize a new InputSource. This InputSource is then
// loaded by the XML data source using the XML reader.
String doc = documentToString();
StringReader reader = new StringReader(doc);
try {
InputSource src = new InputSource(reader);
// Set the system ID to find the DTD.
src.setSystemId(documentId.toString());
xmlDS.load(src, xmlReader);
} catch (Exception e) {
JOptionPane.showMessageDialog(null, "Not a valid JViews Charts XML document.", "Cannot load data !",
JOptionPane.ERROR_MESSAGE);
} finally {
reader.close();
}
// Refresh the text area contents.
textArea.setText(doc);
}
/**
* Configures the specified tree to use a custom <code>CellRenderer</code> and
* <code>CellEditor</code> able to handle <code>org.w3c.dom.Node</code> user
* objects.
*/
protected void configureTree(JTree tree) {
tree.setEditable(true);
// Create a custom cell renderer.
// The text of a tree node is initialized to:
// - the Node value for a Node of type TEXT
// - the Node name + the Node value for a Node of type ATTRIBUTE
// - the Node name for a Node of another type
DefaultTreeCellRenderer cellRenderer = new DefaultTreeCellRenderer() {
Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded,
boolean leaf, int row, boolean hasFocus) {
super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
Node node = (Node) ((DefaultMutableTreeNode) value).getUserObject();
if (node.getNodeType() == Node.TEXT_NODE) {
setText(node.getNodeValue());
if (textIcon != null)
setIcon(textIcon);
} else if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
setText(node.getNodeName() + " : " + node.getNodeValue());
if (attrIcon != null)
setIcon(attrIcon);
} else {
setText(node.getNodeName());
}
return this;
}
};
tree.setCellRenderer(cellRenderer);
// Create a custom editor able to handle org.w3c.dom.Node object.
tree.setCellEditor(new DefaultTreeCellEditor(tree, cellRenderer) {
// During editing, the text field text is initialized to the
// Node value for a Node of type TEXT or ATTRIBUTE or the Node name
// for other types.
Override
public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded,
boolean leaf, int row) {
Component comp = super.getTreeCellEditorComponent(tree, value, isSelected, expanded, leaf, row);
Node node = (Node) ((DefaultMutableTreeNode) value).getUserObject();
if ((node.getNodeType() == Node.TEXT_NODE) || (node.getNodeType() == Node.ATTRIBUTE_NODE))
((JTextField) editingComponent).setText(node.getNodeValue());
else
((JTextField) editingComponent).setText(node.getNodeName());
return comp;
}
// After a cell has been edited, set the value of the corresponding
// Node to the text field text.
Override
public Object getCellEditorValue() {
String value = (String) super.getCellEditorValue();
TreePath path = this.tree.getPathForRow(lastRow);
if (path != null) {
DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) path.getLastPathComponent();
Node node = (Node) treeNode.getUserObject();
if ((node.getNodeType() == Node.TEXT_NODE) || (node.getNodeType() == Node.ATTRIBUTE_NODE))
node.setNodeValue(value);
return node;
}
return value;
}
});
}
/**
* Populates the tree model according to the specified Document.
*/
protected void fillTreeModel(Document doc) {
document = doc;
Element root = document.getDocumentElement();
DefaultMutableTreeNode treeRoot = createTreeNode(root);
treeModel.setRoot(treeRoot);
addToTree(treeRoot, root);
}
private void addToTree(DefaultMutableTreeNode parent, Node node) {
// Every node is associated with its corresponding DOM element.
String value = null;
Node child = node.getFirstChild();
while (child != null) {
if (child.getNodeType() == Node.ELEMENT_NODE) {
DefaultMutableTreeNode childTreeNode = createTreeNode(child);
parent.add(childTreeNode);
NamedNodeMap attributes = child.getAttributes();
for (int i = 0; i < attributes.getLength(); ++i) {
Node attr = attributes.item(i);
DefaultMutableTreeNode attrNode = createTreeNode(attr);
childTreeNode.add(attrNode);
}
addToTree(childTreeNode, child);
} else if ((child.getNodeType() == Node.TEXT_NODE)
&& ((value = child.getNodeValue()).length() > 0 && Character.isJavaIdentifierPart(value.charAt(0)))) {
parent.add(createTreeNode(child));
}
child = child.getNextSibling();
}
}
/**
* Factory method that returns a <code>DefaultMutableTreeNode</code> instance.
*/
protected DefaultMutableTreeNode createTreeNode(Node node) {
return new DefaultMutableTreeNode(node);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
Override
public void run() {
JFrame frame = new JFrame("XMLDemo");
XMLDemo sample = new XMLDemo();
sample.init(frame.getContentPane());
frame.setSize(800, 600);
Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
frame.setLocation(d.width / 2 - frame.getWidth() / 2, d.height / 2 - frame.getHeight() / 2);
frame.setVisible(true);
}
});
}
}