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(); // See if the bean has a custom name for its DOM setter String nameOfGetter = "getAsDOM"; try { Method mNameGetter = classOfBean.getMethod("getDOMGetterName", new Class[] { }); nameOfGetter = (String) (mNameGetter.invoke(bean, new Object[] {})); } catch (Exception ex) { ; // Ignore exceptions -- this either works or it doesn't } // If the bean knows how to encode itself in XML, then // use the DOM document it returns. Method mGetAsDOM = null; try { mGetAsDOM = classOfBean.getMethod(nameOfGetter, new Class[] { org.w3c.dom.Document.class }); if (mGetAsDOM != null) { dfResult = (DocumentFragment) (mGetAsDOM.invoke(bean, new Object[] {doc})); return dfResult; } } catch (Exception e) { ; // Ignore exceptions } // If we found a DOM read method, invoke it, and we're done. if (mGetAsDOM != null) { try { dfResult = (DocumentFragment) mGetAsDOM.invoke(bean, new Object[] {doc}); return dfResult; } catch (Exception e) { ; // Ignore exceptions } } // Get a BeanInfo for the bean. BeanInfo bi = Introspector.getBeanInfo(classOfBean); // If the bean doesn't know how to encode itself in XML, // then create a DOM document by introspecting the bean and // inserting nodes that represent the bean's properties. if (dfResult == null) { dfResult = doc.createDocumentFragment(); 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; } // 0. If pd is an XML property descriptor, and we can call its // getter method, do so and return. If the programmer // specifies a DOM getter method, we return what it returns, no questions asked. if (pd instanceof XMLPropertyDescriptor) { Method getter; if ((getter = ((XMLPropertyDescriptor) pd).getDOMReadMethod()) != null) { try { Class[] params = {org.w3c.dom.Document.class}; Object[] args = {doc}; dfResult = (DocumentFragment) (getter.invoke(bean, args)); } catch (Exception ee) { ; // Ignore... couldn't get the method } return dfResult; } } // 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 getAsDOM() // [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 getAsDOM()? // 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 + "AsDOM"; // 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 && oPropertyValue != 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]); P("Wrote bean to file '" + args[1] + "'"); } catch (Exception ee) { P("Couldn't write JavaBean to file " + args[1]); P(ee.getMessage()); 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)); } }