package com.javaworld.JavaBeans.XMLBeans;
import java.io.*;
import java.beans.*;
import org.w3c.dom.*;
import com.ibm.xml.parser.*;
import java.lang.reflect.*;
/**
* This class contains static methods that write a JavaBean
* to a Writer object, formatted as XML.
*/
public class XMLBeanWriter {
/**
* Constructor is, for the most part, superfluous, since all
* of the object's methods are static.
*/
public XMLBeanWriter() {
super();
}
/**
* Build a DOM DocumentFragment representing the class and properties of
* a JavaBean.
* @return org.w3c.dom.DocumentFragment - the document representing the JavaBean.
* @param doc Creates nodes using this document as a factory.
* @param bean The JavaBean for which we want the DOM tree
* @exception java.beans.IntrospectionException An error occurred during
* introspection of the JavaBean.
*/
public static DocumentFragment getAsDOM(Document doc, Object bean) throws IntrospectionException, InstantiationException, IllegalAccessException {
// Create the fragment we'll return
DocumentFragment dfResult = null;
// Analyze the bean
Class classOfBean = bean.getClass();
// If the bean knows how to encode itself in XML, then
// use the DOM document it returns.
try {
Method mgetAsDOM = classOfBean.getMethod("getAsDOM",
new Class[] { org.w3c.dom.Document.class } );
dfResult = (DocumentFragment) mgetAsDOM.invoke(bean,
new Object[] { doc });
} catch (Exception e) {
; // Ignore exceptions
}
// If the bean doesn't know how to encode itself in XML,
// then create a DOM document by introspecting it.
if (dfResult == null) {
dfResult = doc.createDocumentFragment();
BeanInfo bi = Introspector.getBeanInfo(classOfBean);
PropertyDescriptor[] pds = bi.getPropertyDescriptors();
// Add an Element indicating that this is a JavaBean.
// The element has a single attribute, which is that Element's class.
Element eBean = doc.createElement("JavaBean");
dfResult.appendChild(eBean);
eBean.setAttribute("CLASS", classOfBean.getName());
Element eProperties = doc.createElement("Properties");
eBean.appendChild(eProperties);
// For each property of the bean, get a DocumentFragment that
// represents the individual property. Append that DocumentFragment
// to the Properties element of the document
for (int i = 0; i < pds.length; i++) {
PropertyDescriptor pd = pds[i];
DocumentFragment df = getAsDOM(doc, bean, pd);
if (df != null) {
// Create a Property element and add to Properties element
Element eProperty = doc.createElement("Property");
eProperties.appendChild(eProperty);
// Create NAME attribute, add it to Property element,
// and set it to name of property
eProperty.setAttribute("NAME", pd.getName());
// Append the DocumentFragment to the Property element
// This "splices" the entire DOM representation of the
// Property into the tree at this point.
eProperty.appendChild(df);
}
}
}
return dfResult;
}
/**
* Return a DOM DocumentFragment representing a property of a JavaBean
* @return org.w3c.dom.DocumentFragment
* @param doc The document to use as a factory for
* subelements of the tree.
* @param bean The object that is the value of the property.
* This object must be a JavaBean.
* @param pd A property descriptor describing
* the property of which the object is a value.
* @exception IllegalAccessException
* @exception InstantiationException
* @exception IntrospectionException
*/
public static DocumentFragment getAsDOM(Document doc, Object bean, PropertyDescriptor pd) throws IntrospectionException, InstantiationException, IllegalAccessException {
Class classOfBean = bean.getClass();
Class classOfProperty = pd.getPropertyType();
DocumentFragment dfResult = null;
String sValueAsText = null;
Class[] paramsNone = {};
Object[] argsNone = {};
// If the property is "class", and the type is java.lang.class, then
// this is the class of the bean, which we've already encoded.
// So, in this special case, return null.
if (pd.getName().equals("class") && classOfProperty.equals(java.lang.Class.class)) {
return null;
}
// 1. Try to represent the property as XML.
// This bean may know how to describe itself, or parts of
// itself, as XML. There are two possibilities:
// [a] The bean has a method called getAsXML()
// [b] The property class has a method called getAsDOM()
// We'll try both of these, and the first (if any) that
// works will be the DocumentFragment we want to return.
// If none of these are true, then we try to find the object's
// value as text
// [1a] Does the bean have a method called getAsXML()?
// Capitalize property name
StringBuffer sPropname = new StringBuffer(pd.getName());
char c = sPropname.charAt(0);
if (c >= 'a' && c <= 'z') {
c += 'A' - 'a';
}
sPropname.setCharAt(0, c);
String sXMLGetterName = "get" + sPropname + "AsXML";
// If both of these methods succeed, then dfResult will be set
// to non-null; that is, the method existed and returned a
// DocumentFragment
try {
Class [] params = { org.w3c.dom.Document.class };
Method mXMLGetter = classOfBean.getMethod(sXMLGetterName, params);
Object[] args = { doc };
dfResult = (DocumentFragment) (mXMLGetter.invoke(bean, args));
} catch (Exception ee) {
; // Ignore... couldn't get the method
}
// Hereafter, we're trying to create a representation of the property
// based somehow on the property's value.
// The very first thing we need to do is get the value of the
// property as an object. If we can't do that, we can get no
// representation of the property at all.
Object oPropertyValue = null;
try {
Method getter = pd.getReadMethod();
if (getter != null) {
oPropertyValue = getter.invoke(bean, argsNone);
}
} catch (InvocationTargetException ex) {
; // Couldn't get value. Probably should be an error.
}
// [1b] If we don't have a DocumentFragment, the previous block failed.
// So, let's find out if the property's class has a method called
// getAsDOM() and, if it does, call that instead.
if (dfResult == null) {
try {
Class [] params = { org.w3c.dom.Document.class };
Method mXMLGetter = classOfProperty.getMethod("getAsDOM", params);
Object[] args = { doc };
dfResult = (DocumentFragment) (mXMLGetter.invoke(oPropertyValue, args));
} catch (Exception ee) {
; // Ignore -- who cares why it failed?
}
}
// 2. Try to represent the property as a String.
// See if this property's value
// is something we can represent as Text, or if it's something
// that must be represented as a JavaBean. Let's assume that this
// object can be represented as text if:
// [a] it has a PropertyEditor associated with it, because
// PropertyEditors always have setAsText() and getAsText()
// If it can't be represented as text, then we pass it to
// getAsDOM(Document, Object) and return the result.
if (dfResult == null) {
// [2a] Can we get either a custom or built-in property editor?
// If the PropertyDescriptor returns an editor class, we
// create an instance of it; otherwise, we ask the system for
// a default editor for that class.
Class pedClass = pd.getPropertyEditorClass();
PropertyEditor propEditor = null;
if (pedClass != null) {
propEditor = (PropertyEditor) (pedClass.newInstance());
} else {
propEditor = PropertyEditorManager.findEditor(classOfProperty);
}
// If the property editor's not null, pass the property's
// value to the PropertyEditor, and then ask the PropertyEditor
// for a text representation of the object.
if (propEditor != null) {
propEditor.setValue(oPropertyValue);
sValueAsText = propEditor.getAsText();
}
// If somewhere above we found a string value, then create
// a DocumentFragment to return, and append to it
// a Text element.
if (sValueAsText != null) {
dfResult = doc.createDocumentFragment();
Text textValue = doc.createTextNode(sValueAsText);
dfResult.appendChild(textValue);
}
}
// 3. Try to represent the property value as a JavaBean
// If we don't have a DocumentFragment yet, we'll
// have to introspect the value of the object, because
// it's apparently something that can't be represented
// as flat text. We'll assume it's a JavaBean.
// If it isn't... oh, well.
//
if (dfResult == null) {
dfResult = getAsDOM(doc, oPropertyValue);
}
return dfResult;
}
/**
* This main method tests XMLBeanWriter.writeXMLBean() by loading
* an JavaBean from the file whose name appears as the first argument
* on the command line.
* @param args java.lang.String[]
*/
public static void main(String args[]) {
Object b = null;
try {
b = XMLBeanReader.readXMLBean(args[0]);
try {
Method printer = b.getClass().getMethod("print", null);
P("--- Read a JavaBean of type " + b.getClass().getName());
printer.invoke(b, null);
} catch (Exception ee) {
P("Unable to print JavaBean");
}
} catch (Exception ee) {
P("Couldn't read JavaBean from file " + args[0]);
ee.printStackTrace();
}
// Now, write it back out
if (b != null && args.length > 1) {
try {
XMLBeanWriter.writeXMLBean(b, args[1]);
} catch (Exception ee) {
P("Couldn't write JavaBean to file " + args[1]);
ee.printStackTrace();
}
}
}
/**
* Shorthand method to print to stdout.
* @param s The string to print
*/
static public void p(String s) {
XMLBeanReader.p(s);
}
/**
* Shorthand method to print to stdout, with a newline appended.
* @param s The string to print
*/
static public void P(String s) {
XMLBeanReader.P(s);
}
/**
* Write a JavaBean as XML to a File.
* @param bean The JavaBean to write
* @param file The File to which to write the JavaBean.
* @exception java.io.FileNotFoundException
* @exception java.beans.IntrospectionException
*/
public static void writeXMLBean(Object bean, File file) throws java.io.IOException, java.beans.IntrospectionException,
InstantiationException, IllegalAccessException {
writeXMLBean(bean, new FileWriter(file));
}
/**
* Write a JavaBean as XML to a Writer.
* @param bean The JavaBean to write.
* @param writer The writer to which the XML is written.
* @exception java.beans.IntrospectionException
* @exception IOException
* @exception InstantiationException
* @exception IllegalAccessException
*/
public static void writeXMLBean(Object bean, Writer writer) throws IOException,
java.beans.IntrospectionException,
InstantiationException, IllegalAccessException {
// Create a DOM document tree for this JavaBean and return it
// NOTE: This method specifically references TXDocument,
// which is an xml4j class!
TXDocument doc = new TXDocument();
DocumentFragment df = getAsDOM(doc, bean);
doc.appendChild(df);
// Write out the document as XML to the Writer.
// NOTE AGAIN: Specifically references TXDocument.printWithFormat(),
// which is xml4j-specific!
doc.printWithFormat(writer);
}
/**
* Write a JavaBean, formatted as XML, to a file whose name is passed as sFilename.
* @param bean The JavaBean to write
* @param sFilename The name or path to the file to write.
* @exception java.io.FileNotFoundException
* @exception java.beans.IntrospectionException
*/
public static void writeXMLBean(Object bean, String sFilename) throws java.io.IOException, java.beans.IntrospectionException,
InstantiationException, IllegalAccessException {
writeXMLBean(bean, new FileWriter(sFilename));
}
}