XMLBeanWriter.java
001 package com.javaworld.JavaBeans.XMLBeans;
002 
003 import java.io.*;
004 import java.beans.*;
005 import org.w3c.dom.*;
006 import com.ibm.xml.parser.*;
007 import java.lang.reflect.*;
008 
009 /**
010  * This class contains static methods that write a JavaBean
011  * to a Writer object, formatted as XML.
012  */
013 public class XMLBeanWriter {
014 
015 /**
016  * Constructor is, for the most part, superfluous, since all
017  * of the object's methods are static.
018  */
019 public XMLBeanWriter() {
020 	super();
021 }
022 /**
023  * Build a DOM DocumentFragment representing the class and properties of
024  * a JavaBean.
025  * @return org.w3c.dom.DocumentFragment - the document representing the JavaBean.
026  * @param doc Creates nodes using this document as a factory.
027  * @param bean The JavaBean for which we want the DOM tree
028  * @exception java.beans.IntrospectionException An error occurred during
029  * introspection of the JavaBean.
030  */
031 public static DocumentFragment getAsDOM(Document doc, Object bean) throws IntrospectionException, InstantiationException, IllegalAccessException {
032 
033 	// Create the fragment we'll return
034 	DocumentFragment dfResult = null;
035 
036 	// Analyze the bean
037 	Class classOfBean = bean.getClass();
038 
039 	// See if the bean has a custom name for its DOM setter
040 	String nameOfGetter = "getAsDOM";
041 	try {
042 		Method mNameGetter = classOfBean.getMethod("getDOMGetterName", new Class[] {  });
043 		nameOfGetter = (String) (mNameGetter.invoke(bean, new Object[] {}));
044 	} catch (Exception ex) {
045 		; // Ignore exceptions -- this either works or it doesn't
046 	}
047 
048 	// If the bean knows how to encode itself in XML, then
049 	// use the DOM document it returns.
050 	Method mGetAsDOM = null;
051 	try {
052 		mGetAsDOM = classOfBean.getMethod(nameOfGetter, new Class[] { org.w3c.dom.Document.class });
053 		if (mGetAsDOM != null) {
054 			dfResult = (DocumentFragment) (mGetAsDOM.invoke(bean, new Object[] {doc}));
055 			return dfResult;
056 		}
057 	} catch (Exception e) {
058 		; // Ignore exceptions
059 	}
060 
061 	// If we found a DOM read method, invoke it, and we're done.
062 	if (mGetAsDOM != null) {
063 		try {
064 			dfResult = (DocumentFragment) mGetAsDOM.invoke(bean, new Object[] {doc});
065 			return dfResult;
066 		} catch (Exception e) {
067 			; // Ignore exceptions
068 		}
069 	}
070 
071 	// Get a BeanInfo for the bean.
072 	BeanInfo bi = Introspector.getBeanInfo(classOfBean);
073 
074 	// If the bean doesn't know how to encode itself in XML,
075 	// then create a DOM document by introspecting the bean and
076 	// inserting nodes that represent the bean's properties.
077 	if (dfResult == null) {
078 		dfResult = doc.createDocumentFragment();
079 		PropertyDescriptor[] pds = bi.getPropertyDescriptors();
080 
081 		// Add an Element indicating that this is a JavaBean.
082 		// The element has a single attribute, which is that Element's class.
083 		Element eBean = doc.createElement("JavaBean");
084 		dfResult.appendChild(eBean);
085 		eBean.setAttribute("CLASS", classOfBean.getName());
086 		Element eProperties = doc.createElement("Properties");
087 		eBean.appendChild(eProperties);
088 
089 		// For each property of the bean, get a DocumentFragment that
090 		// represents the individual property. Append that DocumentFragment
091 		// to the Properties element of the document
092 		for (int i = 0; i < pds.length; i++) {
093 			PropertyDescriptor pd = pds[i];
094 			DocumentFragment df = getAsDOM(doc, bean, pd);
095 			if (df != null) {
096 				// Create a Property element and add to Properties element
097 				Element eProperty = doc.createElement("Property");
098 				eProperties.appendChild(eProperty);
099 
100 				// Create NAME attribute, add it to Property element,
101 				// and set it to name of property
102 				eProperty.setAttribute("NAME", pd.getName());
103 
104 				// Append the DocumentFragment to the Property element
105 				// This "splices" the entire DOM representation of the
106 				// Property into the tree at this point.
107 				eProperty.appendChild(df);
108 			}
109 		}
110 	}
111 	return dfResult;
112 }
113 /**
114  * Return a DOM DocumentFragment representing a property of a JavaBean
115  * @return org.w3c.dom.DocumentFragment
116  * @param doc The document to use as a factory for
117  * subelements of the tree.
118  * @param bean The object that is the value of the property.
119  * This object must be a JavaBean.
120  * @param pd A property descriptor describing
121  * the property of which the object is a value.
122  * @exception IllegalAccessException
123  * @exception InstantiationException
124  * @exception IntrospectionException
125  */
126 public static DocumentFragment getAsDOM(Document doc, Object bean, PropertyDescriptor pd) throws IntrospectionException, InstantiationException, IllegalAccessException {
127 	Class classOfBean = bean.getClass();
128 	Class classOfProperty = pd.getPropertyType();
129 	DocumentFragment dfResult = null;
130 	String sValueAsText = null;
131 	Class[] paramsNone = {};
132 	Object[] argsNone = {};
133 
134 	// If the property is "class", and the type is java.lang.class, then
135 	// this is the class of the bean, which we've already encoded.
136 	// So, in this special case, return null.
137 	if (pd.getName().equals("class") && classOfProperty.equals(java.lang.Class.class)) {
138 		return null;
139 	}
140 
141 	// 0. If pd is an XML property descriptor, and we can call its
142 	// getter method, do so and return. If the programmer
143 	// specifies a DOM getter method, we return what it returns, no questions asked.
144 	if (pd instanceof XMLPropertyDescriptor) {
145 		Method getter;
146 		if ((getter = ((XMLPropertyDescriptor) pd).getDOMReadMethod()) != null) {
147 			try {
148 				Class[] params = {org.w3c.dom.Document.class};
149 				Object[] args = {doc};
150 				dfResult = (DocumentFragment) (getter.invoke(bean, args));
151 			} catch (Exception ee) {
152 				; // Ignore... couldn't get the method
153 			}
154 			return dfResult;
155 		}
156 	}
157 
158 	// 1. Try to represent the property as XML.
159 	// This bean may know how to describe itself, or parts of
160 	// itself, as XML. There are two possibilities:
161 	// [a] The bean has a method called getAsDOM()
162 	// [b] The property class has a method called getAsDOM()
163 	// We'll try both of these, and the first (if any) that
164 	// works will be the DocumentFragment we want to return.
165 	// If none of these are true, then we try to find the object's
166 	// value as text
167 
168 	// [1a] Does the bean have a method called getAsDOM()?
169 	// Capitalize property name
170 	StringBuffer sPropname = new StringBuffer(pd.getName());
171 	char c = sPropname.charAt(0);
172 	if (c >= 'a' && c <= 'z') {
173 		c += 'A' - 'a';
174 	}
175 	sPropname.setCharAt(0, c);
176 	String sXMLGetterName = "get" + sPropname + "AsDOM";
177 
178 	// If both of these methods succeed, then dfResult will be set
179 	// to non-null; that is, the method existed and returned a
180 	// DocumentFragment
181 	try {
182 		Class[] params = {org.w3c.dom.Document.class};
183 		Method mXMLGetter = classOfBean.getMethod(sXMLGetterName, params);
184 		Object[] args = {doc};
185 		dfResult = (DocumentFragment) (mXMLGetter.invoke(bean, args));
186 	} catch (Exception ee) {
187 		; // Ignore... couldn't get the method
188 	}
189 
190 	// Hereafter, we're trying to create a representation of the property
191 	// based somehow on the property's value.
192 	// The very first thing we need to do is get the value of the
193 	// property as an object. If we can't do that, we can get no
194 	// representation of the property at all.
195 	Object oPropertyValue = null;
196 	try {
197 		Method getter = pd.getReadMethod();
198 		if (getter != null) {
199 			oPropertyValue = getter.invoke(bean, argsNone);
200 		}
201 	} catch (InvocationTargetException ex) {
202 		; // Couldn't get value. Probably should be an error.
203 	}
204 
205 	// [1b] If we don't have a DocumentFragment, the previous block failed.
206 	// So, let's find out if the property's class has a method called
207 	// getAsDOM() and, if it does, call that instead.
208 	if (dfResult == null) {
209 		try {
210 			Class[] params = {org.w3c.dom.Document.class};
211 			Method mXMLGetter = classOfProperty.getMethod("getAsDOM", params);
212 			Object[] args = {doc};
213 			dfResult = (DocumentFragment) (mXMLGetter.invoke(oPropertyValue, args));
214 		} catch (Exception ee) {
215 			; // Ignore -- who cares why it failed?
216 		}
217 	}
218 
219 
220 	// 2. Try to represent the property as a String.
221 	// See if this property's value
222 	// is something we can represent as Text, or if it's something
223 	// that must be represented as a JavaBean. Let's assume that this
224 	// object can be represented as text if:
225 	// [a] it has a PropertyEditor associated with it, because
226 	// PropertyEditors always have setAsText() and getAsText()
227 	// If it can't be represented as text, then we pass it to
228 	// getAsDOM(Document, Object) and return the result.
229 
230 	if (dfResult == null) {
231 
232 		// [2a] Can we get either a custom or built-in property editor?
233 		// If the PropertyDescriptor returns an editor class, we
234 		// create an instance of it; otherwise, we ask the system for
235 		// a default editor for that class.
236 		Class pedClass = pd.getPropertyEditorClass();
237 		PropertyEditor propEditor = null;
238 		if (pedClass != null) {
239 			propEditor = (PropertyEditor) (pedClass.newInstance());
240 		} else {
241 			propEditor = PropertyEditorManager.findEditor(classOfProperty);
242 		}
243 
244 		// If the property editor's not null, pass the property's
245 		// value to the PropertyEditor, and then ask the PropertyEditor
246 		// for a text representation of the object.
247 		if (propEditor != null) {
248 			propEditor.setValue(oPropertyValue);
249 			sValueAsText = propEditor.getAsText();
250 		}
251 
252 		// If somewhere above we found a string value, then create
253 		// a DocumentFragment to return, and append to it
254 		// a Text element.
255 		if (sValueAsText != null) {
256 			dfResult = doc.createDocumentFragment();
257 			Text textValue = doc.createTextNode(sValueAsText);
258 			dfResult.appendChild(textValue);
259 		}
260 	}
261 
262 	// 3. Try to represent the property value as a JavaBean
263 	// If we don't have a DocumentFragment yet, we'll
264 	// have to introspect the value of the object, because
265 	// it's apparently something that can't be represented
266 	// as flat text. We'll assume it's a JavaBean.
267 	// If it isn't... oh, well.
268 	//
269 	if (dfResult == null && oPropertyValue != null) {
270 		dfResult = getAsDOM(doc, oPropertyValue);
271 	}
272 	return dfResult;
273 }
274 /**
275  * This main method tests XMLBeanWriter.writeXMLBean() by loading
276  * an JavaBean from the file whose name appears as the first argument
277  * on the command line.
278  * @param args java.lang.String[]
279  */
280 public static void main(String args[]) {
281 	Object b = null;
282 	try {
283 		b = XMLBeanReader.readXMLBean(args[0]);
284 		try {
285 			Method printer = b.getClass().getMethod("print", null);
286 			P("--- Read a JavaBean of type " + b.getClass().getName());
287 			printer.invoke(b, null);
288 		} catch (Exception ee) {
289 			P("Unable to print JavaBean");
290 		}
291 	} catch (Exception ee) {
292 		P("Couldn't read JavaBean from file " + args[0]);
293 		ee.printStackTrace();
294 	}
295 
296 	// Now, write it back out
297 	if (b != null && args.length > 1) {
298 		try {
299 			XMLBeanWriter.writeXMLBean(b, args[1]);
300 			P("Wrote bean to file '" + args[1] + "'");
301 		} catch (Exception ee) {
302 			P("Couldn't write JavaBean to file " + args[1]);
303 			P(ee.getMessage());
304 			ee.printStackTrace();
305 		}
306 	}
307 }
308 /**
309  * Shorthand method to print to stdout.
310  * @param s The string to print
311  */
312 static public void p(String s) {
313 	XMLBeanReader.p(s);
314 }
315 /**
316  * Shorthand method to print to stdout, with a newline appended.
317  * @param s The string to print
318  */
319 static public void P(String s) {
320 	XMLBeanReader.P(s);
321 }
322 /**
323  * Write a JavaBean as XML to a File. 
324  * @param bean The JavaBean to write
325  * @param file The File to which to write the JavaBean.
326  * @exception java.io.FileNotFoundException
327  * @exception java.beans.IntrospectionException
328  */
329 public static void writeXMLBean(Object bean, File file) throws java.io.IOException, java.beans.IntrospectionException,
330 InstantiationException, IllegalAccessException {
331 	writeXMLBean(bean, new FileWriter(file));
332 }
333 /**
334  * Write a JavaBean as XML to a Writer. 
335  * @param bean The JavaBean to write.
336  * @param writer The writer to which the XML is written.
337  * @exception java.beans.IntrospectionException
338  * @exception IOException
339  * @exception InstantiationException
340  * @exception IllegalAccessException
341  */
342 public static void writeXMLBean(Object bean, Writer writer) throws IOException,
343 java.beans.IntrospectionException,
344 InstantiationException, IllegalAccessException {
345 	// Create a DOM document tree for this JavaBean and return it
346 	// NOTE: This method specifically references TXDocument,
347 	// which is an xml4j class!
348 	TXDocument doc = new TXDocument();
349 	DocumentFragment df = getAsDOM(doc, bean);
350 	doc.appendChild(df);
351 
352 	// Write out the document as XML to the Writer.
353 	// NOTE AGAIN: Specifically references TXDocument.printWithFormat(),
354 	// which is xml4j-specific!
355 	doc.printWithFormat(writer);
356 
357 }
358 /**
359  * Write a JavaBean, formatted as XML, to a file whose name is passed as sFilename.
360  * @param bean The JavaBean to write
361  * @param sFilename The name or path to the file to write.
362  * @exception java.io.FileNotFoundException 
363  * @exception java.beans.IntrospectionException 
364  */
365 public static void writeXMLBean(Object bean, String sFilename) throws java.io.IOException, java.beans.IntrospectionException,
366 InstantiationException, IllegalAccessException {
367 	writeXMLBean(bean, new FileWriter(sFilename));
368 }
369 }