/*
* $Id$
*
* Copyright (c) 2000-2003 by Rodney Kinney
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License (LGPL) as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, copies are available
* at http://www.opensource.org.
*/
package VASSAL.build;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import VASSAL.tools.DataArchive;
import VASSAL.tools.ErrorDialog;
import VASSAL.tools.ThrowableUtils;
import VASSAL.tools.io.IOUtils;
/**
* This class holds static convenience methods for building {@link Buildable}
* objects.
*/
public abstract class Builder {
private static final Logger logger = LoggerFactory.getLogger(Builder.class);
/**
* General building algorithm. For each subelement of the build
* Element, this method creates an instance of the class (which
* must implement Buildable) whose name matches the XML element
* tag name, builds that instance with the subelement, and adds it
* to the parent Buildable
*
* This algorithm calls a component's {@link Buildable#build} method
* before calling its {@link Buildable#addTo} method
*
* @param parent the parent Buildable instance
*/
public static void build(Element e, Buildable parent) {
if (e == null) return;
for (Node child = e.getFirstChild(); child != null;
child = child.getNextSibling()) {
if (Node.ELEMENT_NODE == child.getNodeType()) {
try {
final Buildable b = create((Element) child);
if (parent != null) {
b.addTo(parent);
parent.add(b);
}
}
catch (IllegalBuildException ex) {
ErrorDialog.bug(ex);
}
catch (RuntimeException ex) {
logger.error("Error building " + child.getNodeName());
throw ex;
}
catch (Error ex) {
logger.error("Error building " + child.getNodeName());
throw ex;
}
}
}
}
/**
* Create an instance of a class from an XML element and build it.
*
* The <code>.class</code> file for the named class may be either
* in the System's classpath or else within the {@link DataArchive}
* of the {@link GameModule}.
*
* @throws IllegalBuildException if something goes wrong when loading
* the class or creating an instance of it
*/
public static Buildable create(Element e) throws IllegalBuildException {
final GameModule mod = GameModule.getGameModule();
final String name = e.getTagName();
try {
final Buildable b = (Buildable) (mod == null ? Class.forName(name) :
mod.getDataArchive().loadClass(name)).getConstructor().newInstance();
b.build(e);
return b;
}
catch (Throwable t) {
// find and rethrow causes which are not bugs
ThrowableUtils.throwRecent(OutOfMemoryError.class, t);
if (t instanceof ClassCastException ||
t instanceof ClassNotFoundException ||
t instanceof IllegalAccessException ||
t instanceof IllegalArgumentException ||
t instanceof InstantiationException ||
t instanceof InvocationTargetException ||
t instanceof NoSuchMethodException ||
t instanceof SecurityException ||
t instanceof ExceptionInInitializerError ||
t instanceof LinkageError) {
// one of the standard classloading problems occured
throw new IllegalBuildException("failed to load class " + name, t);
}
else if (t instanceof Error) {
// some unusual problem occurred
throw (Error) t;
}
else if (t instanceof RuntimeException) {
// some unusual problem occurred
throw (RuntimeException) t;
}
else {
// this should never happen
throw new IllegalStateException(t);
}
}
}
/**
* Read an XML document from an InputStream
*/
public static Document createDocument(InputStream in)
throws IOException {
try {
final Document doc = DocumentBuilderFactory.newInstance()
.newDocumentBuilder()
.parse(in);
in.close();
return doc;
}
catch (ParserConfigurationException e) {
ErrorDialog.bug(e);
return null;
}
catch (SAXException e) {
// FIXME: switch to IOException(Throwable) ctor in Java 1.6
throw (IOException) new IOException().initCause(e);
}
finally {
IOUtils.closeQuietly(in);
}
}
/**
* Create a new XML document
*/
public static Document createNewDocument() {
try {
return DocumentBuilderFactory.newInstance()
.newDocumentBuilder()
.newDocument();
}
catch (ParserConfigurationException e) {
ErrorDialog.bug(e);
return null;
}
}
/**
* Write an XML document to a Writer
*/
public static void writeDocument(Document doc, Writer writer)
throws IOException {
final Source source = new DOMSource(doc);
// Prepare the output file
final Result result = new StreamResult(writer);
// Write the DOM document to the file
try {
final Transformer xformer =
TransformerFactory.newInstance().newTransformer();
xformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$
xformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); //$NON-NLS-1$ //$NON-NLS-2$
xformer.transform(source, result);
}
catch (TransformerException e) {
// FIXME: switch to IOException(Throwable) ctor in Java 1.6
throw (IOException) new IOException().initCause(e);
}
}
/**
* Return the decoded text contents of an Element node
*/
public static String getText(Element e) {
final StringBuilder buffer = new StringBuilder();
final NodeList sub = e.getChildNodes();
for (int i = 0; i < sub.getLength(); ++i) {
if (sub.item(i).getNodeType() == Node.TEXT_NODE) {
buffer.append(((org.w3c.dom.Text) sub.item(i)).getData());
}
else if (sub.item(i).getNodeType() == Node.ENTITY_REFERENCE_NODE) {
buffer.append(sub.item(i).getFirstChild().toString());
}
}
return buffer.toString().trim();
}
/**
* @return a String representation of an XML document
*/
public static String toString(Document doc) {
final StringWriter w = new StringWriter();
try {
writeDocument(doc, w);
return w.toString();
}
// FIXME: review error message
catch (IOException e) {
// IOErrorDialog.error(e);
return ""; //$NON-NLS-1$
}
}
public static void main(String args[]) {
Document doc = createNewDocument();
Element e = doc.createElement("test"); //$NON-NLS-1$
Element e1 = doc.createElement("sub1"); //$NON-NLS-1$
e.appendChild(e1);
Element e2 = doc.createElement("sub2"); //$NON-NLS-1$
e2.setAttribute("one", "1"); //$NON-NLS-1$ //$NON-NLS-2$
e2.setAttribute("two", "2"); //$NON-NLS-1$ //$NON-NLS-2$
e.appendChild(e2);
Element e3 = doc.createElement("sub3"); //$NON-NLS-1$
Element e4 = doc.createElement("sub4"); //$NON-NLS-1$
e4.appendChild(doc.createTextNode("4 > 2")); //$NON-NLS-1$
e3.appendChild(e4);
e.appendChild(e3);
doc.appendChild(e);
System.err.println(toString(doc));
System.err.println("StringBuilder"); //$NON-NLS-1$
StringBuilder buf = new StringBuilder(300000);
for (int i = 0; i < 500000; ++i) {
buf.append(" "); //$NON-NLS-1$
if (i % 10000 == 0) {
System.err.println(buf.length()); //$NON-NLS-1$
}
}
}
}