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 }