XMLBeanReader.java
001 package com.javaworld.JavaBeans.XMLBeans;
002 
003 import java.beans.*;
004 import java.io.*;
005 import org.w3c.dom.*;
006 //import com.ibm.xml.parser.*;
007 import java.lang.reflect.*;
008 import java.util.*;
009 
010 import org.xml.sax.helpers.ParserFactory;
011 import com.ibm.xml.parsers.*;
012 import org.w3c.dom.Document;
013 import org.xml.sax.*;
014 
015 import com.javaworld.JavaBeans.XMLBeans.*;
016 
017 public class XMLBeanReader {
018 
019 /** Default constructor
020  */
021   public XMLBeanReader() {
022   }  
023 /**
024  * If pd is null, return the object's setAsDOM() method, or null if there isn't one.
025  * Otherwise, if the pd is an XMLPropertyDescriptor, return its DOMWriteMethod.
026  * Otherwise, if there's a getPROPERTYNAMEAsDOM() method in the object,
027  * return that method. If all of these possibilities fail, return null.
028  * @return java.lang.reflect.Method
029  * @param pd java.beans.PropertyDescriptor
030  */
031 protected static Method getDOMSetter(Object o, PropertyDescriptor pd) {
032 
033    // If no property descriptor, it's an object. So, look for the
034    // setAsDOM() method.
035    if (pd == null) {
036       return getDOMSetterMethod(o);
037    }
038 
039    // Does the PropertyDescriptor know how to set as DOM?
040    if (pd instanceof XMLPropertyDescriptor) {
041       XMLPropertyDescriptor xpd = (XMLPropertyDescriptor)pd;
042       return xpd.getDOMWriteMethod();
043    }
044 
045    // Does the bean to which this property belongs have a
046    // method called getAsDOM(), which returns a DocumentFragment?
047    Class objectType = o.getClass();
048    String propName = pd.getName();
049    String getterName = "set" + Util.capitalize(propName) + "AsDOM";
050 
051    Method mResult = null;
052    try {
053       mResult = objectType.getMethod(getterName,
054             new Class[] { org.w3c.dom.Element.class } );
055       if (mResult.getReturnType() == Void.TYPE) {
056          return mResult;
057       }
058    } catch (Exception ex) {
059       ; // It either exists or it doesn't
060    }
061 
062    // Couldn't find anything.
063    return null;
064 }
065 
066 /*
067  * Given a JavaBean, return a method that will initialize the
068  * bean from a given DOM tree. That method will be
069  * void setAsDOM(Node n)
070  * @return java.lang.reflect.Method
071  * @param bean java.lang.Object
072  */
073 public static Method getDOMSetterMethod(Object bean) {
074    Method mResult = null;
075    Class classOfBean = bean.getClass();
076 
077    // See if the bean has a custom name for its DOM setter
078    String nameOfSetter = "setAsDOM";
079    try {
080       Method mNameGetter = classOfBean.getMethod("getDOMSetterName", new Class[] {});
081       nameOfSetter = (String) (mNameGetter.invoke(bean, new Object[] {}));
082    } catch (Exception ex) {
083       ; // Ignore exceptions -- this either works or it doesn't
084    }
085    
086    Class[] classParams = {org.w3c.dom.Node.class};
087    try {
088       mResult = classOfBean.getMethod(nameOfSetter, classParams);
089    } catch (NoSuchMethodException ex) {
090       ; // Ignore
091    }
092    
093    return mResult;
094 }
095 
096 /**
097  * Return an object of type objectType, initialized from the contents of element.
098  * Return null if the objectType is not primitive, or if the element isn't just text.
099  * @return java.lang.Object
100  * @param objectType java.lang.Class
101  * @param element org.w3c.dom.Element
102  */
103 protected static Object getPrimitive(Class objectType, Element element) {
104 
105    // If it's not primitive, we have nothing to do.
106    if (!objectType.isPrimitive()) {
107       return null;
108    }
109 
110    // Find first nonempty text node. Return false if there's anything here
111    // but comments or Text nodes.
112    int i = 0;
113    String stringValue = null;
114    NodeList nl = element.getChildNodes();
115    for (i = 0; i < nl.getLength(); i++) {
116       Node n = nl.item(i);
117 
118       // If it's a comment, skip
119       if (n instanceof Comment) {
120          continue;
121       }
122 
123       // If it's Text, but contains only white space, skip
124       if (n instanceof Text) {
125          String text = ((Text) n).getData();
126          if (text.trim().equals("")) {
127             continue;
128          } else {
129             stringValue = text;
130             break;
131          }
132       }
133 
134       // If it's anything but Text or a comment, there's a problem, because
135       // there's no sensible way to map a tree to a primitive type. So we
136       // complain and return false.
137       Pe("Tried to create a " + objectType.getName() + " with a non-string");
138       return null;
139    }
140 
141    // If empty, we use empty string. Notice there's a problem here: how
142    // do we represent property values that are null? (Exercise for the reader)
143    if (stringValue == null) {
144       stringValue = "";
145    }
146    
147    // Get the argument list for the setter
148    Object setterArg = getPrimitiveSetterArg(objectType, stringValue);
149    return setterArg;
150 }
151 
152 /**
153  * Return an array of one object, which can be used to call a property setter,
154  * for a property that is of a primitive type.
155  * @return java.lang.Object[]
156  * @param propType java.lang.Class
157  * @param sValue java.lang.String
158  */
159 protected static Object getPrimitiveSetterArg(Class propType, String sValue) {
160    Object setterArg = null;
161 
162    // All primitive types except for Character have a constructor
163    // that uses "String" as an argument. We encode single characters as
164    // a number, so we decode them as such.
165    if (propType == char.class) {
166       char c = (char) (Integer.decode(sValue).intValue());
167       setterArg = new Character(c);
168    } else {
169       // Select wrapper class
170       Class wrapper;
171       if (propType == boolean.class)
172          wrapper = Boolean.class;
173       else
174          if (propType == byte.class)
175             wrapper = Byte.class;
176          else
177             if (propType == int.class)
178                wrapper = Integer.class;
179             else
180                if (propType == long.class)
181                   wrapper = Long.class;
182                else
183                   if (propType == short.class)
184                      wrapper = Short.class;
185                   else
186                      if (propType == float.class)
187                         wrapper = Float.class;
188                      else
189                         if (propType == double.class)
190                            wrapper = Double.class;
191                         else {
192                            return null;
193                         }
194 
195                         // Get the constructor for the type that takes a
196                         // String as an argument
197       try {
198          P("Getting ctor");
199          Class[] argTypes = {String.class};
200          Constructor ctor = wrapper.getConstructor(argTypes);
201          P("ctor is " + isNull(ctor));
202 
203          // Create an instance of the object the setter is expecting
204          Object[] ctorArgs = {sValue};
205          setterArg = ctor.newInstance(ctorArgs);
206 
207       } catch (Exception exc) {
208          ; // Ignore -- return null
209       }
210    }
211    return setterArg;
212 }
213 
214 /** Use the Introspector to return an array of property descriptors for
215  * bean class c. If there aren't any, return an empty list.
216  * @param class The class to analyze.
217  * @return java.beans.PropertyDescriptor
218  * @exception java.beans.IntrospectionException
219  */
220 protected static PropertyDescriptor[] getPropertyDescriptors(Class c) {
221    try {
222       BeanInfo beaninfo = Introspector.getBeanInfo(c);
223       return beaninfo.getPropertyDescriptors();
224    } catch (IntrospectionException ex) {
225       Pe("Can't find property descriptors for " + c.getName());
226    }
227    return new PropertyDescriptor[0];
228 }
229 
230 /**
231  * If this node is an Element, return its tag name, UNLESS
232  * the tag name of the element is "Property"; in which case,
233  * return the value of the "NAME" attribute.
234  * @return java.lang.String
235  * @param n org.w3c.dom.Node
236  */
237 protected static String getPropertyName(Node n) {
238    if (n instanceof Element) {
239       Element e = (Element) n;
240       if (e.getTagName().equals("Property"))
241          return e.getAttribute("NAME");
242       return e.getTagName();
243    }
244    return null;
245 }
246 
247 /**
248  * Return a Class object indicating the type of the property. This is
249  * a bit more complex than asking the PropertyDescriptor for it, because
250  * the Element may indicate using another type; for example, a subtype of
251  * the PropertyDescriptor type. This method enforces the rule that the
252  * "declared type" in the element must be assignable to the type indicated
253  * by the property descriptor, if pd is not null.
254  * @return java.lang.Class
255  * @param e org.w3c.dom.Element
256  * @param pd java.beans.PropertyDescriptor
257  */
258 protected static Class getPropertyType(Element element, PropertyDescriptor pd) {
259    Class cResult = null;
260    String className = null;
261 
262    // Is the Element of the form ?
263    if (element.getTagName().equals("Property")) {
264 
265       // Does the Element have a subnode with tagname "JavaBean", which
266       // carries a class attribute?
267       int i;
268       NodeList nl = element.getChildNodes();
269       for (i = 0; i < nl.getLength() && className == null; i++) {
270          if (nl.item(i) instanceof Element) {
271             Element e = (Element) (nl.item(i));
272             if (e.getTagName().equals("JavaBean")) {
273                className = e.getAttribute("CLASS");
274             }
275          }
276       }
277    }
278 
279    // If there's no JavaBean element, maybe this element has a CLASS attribute.
280    // This is along the lines of
281    // , where Histogram is the
282    // property name, and CLASS gives the type of histogram (Histogram
283    // may be abstract).
284    if (className == null) {
285       className = element.getAttribute("CLASS");
286    }
287 
288    // If there's still no class name, try to get the class from
289    // the property descriptor
290    if (className == null || className.equals("")) {
291       if (pd != null) {
292          cResult = pd.getPropertyType();
293       } else {
294          className = element.getTagName();
295       }
296    }
297 
298    // If there isn't a result class yet, try to load the class called ClassName
299    if (cResult == null && className != null && !className.equals("")) {
300       try {
301          cResult = Class.forName(className);
302       } catch (ClassNotFoundException ex) {
303          Pe("Couldn't load class " + className);
304          ex.printStackTrace();
305       }
306    }
307 
308    // Check to be sure that property type and declared type are
309    // assignment compatible, if possible
310    if (cResult != null && pd != null) {
311       Class pdType = pd.getPropertyType();
312       if (!pdType.isAssignableFrom(cResult)) {
313          Pe("Declared class " + cResult.getName() + " is not " + "assignable to property type " + pdType.getName());
314          return null;
315       }
316    }
317 
318    // If we still don't have a result type, use whatever the PropertyDescriptor says
319    if (cResult == null && pd != null) {
320       cResult = pd.getPropertyType();
321    }
322    return cResult;
323 }
324 
325 /**
326  * Return an argument list for a "write" accessor ("setter") for a JavaBean.
327  * See comments for descriptions.
328  * @return java.lang.Object[] - The argument list for the setter.
329  * @param setter java.lang.Method - The setter method.
330  * @param jb java.lang.Object - The JavaBean to receive the set operation
331  * @param e org.w3c.dom.Element - The element containing the data.
332  * @param pd java.beans.PropertyDescriptor - A descriptor describing the property.
333  */
334 protected static Object[] getSetterArgs(Method setter, Object jb, Element e, PropertyDescriptor pd, String sValue, Element eJavaBean) throws IntrospectionException, InvalidPropertyException {
335    String sPropname = pd.getName();
336    Object[] setterArgs = null;
337    Class propType = pd.getPropertyType();
338 
339    // The easiest solution is to use the property's property editor set the
340    // value using setText(), and then get the value as an object using
341    // getValue(). This is especially cool because it works with custom
342    // property editors. Possible problems here: property editor can't
343    // be instantiated for some reason, the property can't be
344    // represented as text (setText() simply throws an exception in this
345    // case), etc.
346    try {
347       PropertyEditor ped = null;
348       Class classPed = pd.getPropertyEditorClass();
349       // Either get the property editor from the descriptor, or
350       // ask the PropertyEditorManager for a default one.
351       if (classPed != null) {
352          ped = (PropertyEditor) (classPed.newInstance());
353       } else {
354          ped = PropertyEditorManager.findEditor(propType);
355       }
356 
357       if (ped != null) {
358          ped.setAsText(sValue);
359          setterArgs = new Object[] {ped.getValue()};
360       }
361    } catch (Exception ex) {
362       ; // If anything fails here, it didn't work
363    }
364 
365    // Well, the property editor approach didn't work. Let's try
366    // something else.
367    if (setterArgs == null) {
368       // Is this property of a primitive type? If so, convert the String
369       // to that type as appropriate, and call "set" function. Simple.
370       if (propType == null) {
371          throw new IntrospectionException("Property " + pd.getName() + " has no type");
372       }
373       P("Property type of " + sPropname + " is " + propType.toString());
374 
375       // If the argument is primitive, create an instance of that primitive
376       // type, and set it using its constructor from String.
377       if (propType.isPrimitive()) {
378          setterArgs = null; // FIXME getPrimitiveSetterArgs(sPropname, propType, sValue);
379       } else {
380 
381          // Handle non-primitive types.
382          // 1. If it's a JavaBean, instantiate the JavaBean it
383          // represents, use the new Bean to set the property.
384          // 2. If the setter() method for the property takes
385          // a single string as its argument, call that setter
386          // method.
387          // 3. If we can construct a sValue of the type corresponding
388          // to the property's type, do so, and then call the
389          // property's setter method.
390          // Otherwise, we just can't set this property.
391          // So, set the property based on one of these strategies:
392 
393          // [1] Is this property's type represented as a JavaBean
394          // in the XML document? The "CLASS" attribute of any
395          // JavaBean that appears here must be assignable to the propType
396          // in order for this to make sense. Note that the "CLASS"
397          // of the bean in the XML tree may be either of the property
398          // type OR a subclass of that property type.
399          if (eJavaBean != null) {
400             setterArgs = getSetterArgsAsJavaBean(sPropname, propType, eJavaBean);
401          }
402 
403          // [2] Does the "setter" function take a single string
404          // as its argument? If so, use "sValue" to set it.
405          if (setterArgs == null) {
406             Class[] setterParameterTypes = setter.getParameterTypes();
407             P("Setter has " + setterParameterTypes.length + " args");
408             if (setterParameterTypes.length == 1 && setterParameterTypes[0] == java.lang.String.class) {
409                setterArgs = new Object[] {sValue};
410             }
411          }
412 
413          // [3] Can we create one of these by passing a String to
414          // a ctor?
415          if (setterArgs == null) {
416             Constructor ctor = null;
417             try {
418                ctor = propType.getConstructor(new Class[] {String.class});
419             } catch (Exception exc) {
420                // Ignore exceptions
421             }
422 
423             // If we got a constructor, use it to create the argument
424             // to the setter, then call the setter
425             if (ctor != null) {
426                try {
427                   Object arg = ctor.newInstance(new Object[] {sValue});
428                   setterArgs = new Object[] {arg};
429                } catch (Exception exc) {
430                   throw new InvalidPropertyException("Exception while trying to create " + "argument for " + sPropname + " setter:\n" + exc.toString());
431                }
432             } else {
433                // We can't construct the object with a string
434                throw new InvalidPropertyException("Can't create " + propType.getName() + " for property '" + sPropname + "'");
435             }
436          }
437       }
438    }
439    return setterArgs;
440 }
441 
442 /**
443  * Load setter args from an Element, and return as an object to be sent to a setter.
444  * @return java.lang.Object[]
445  * @param sPropname java.lang.String
446  * @param eJavaBean org.w3c.dom.Element
447  * @exception com.javaworld.JavaBeans.XMLBeans.InvalidPropertyException The exception description.
448  */
449 protected static Object[] getSetterArgsAsJavaBean(String sPropname, Class propType, Element eJavaBean)
450    throws com.javaworld.JavaBeans.XMLBeans.InvalidPropertyException {
451 
452    Object[] setterArgs = null;
453    String jbClassName = JBClassName(eJavaBean);
454    P("jbClassName = " + jbClassName);
455 
456    // Check to see if it's possible to assign the JavaBean
457    // class in the document to the class of the property
458    // If so, instantiate the JavaBean from the document node
459    // return as an argument list.
460    if (jbClassName != null) {
461       P("loading JB");
462       try {
463          Class docBeanClass = Class.forName(jbClassName);
464          if (propType.isAssignableFrom(docBeanClass)) {
465             Object argBean = instantiateBean(eJavaBean);
466 
467             // Invoke the setter with that argument
468             setterArgs = new Object[] {argBean};
469          }
470       } catch (Exception exc) {
471          throw new InvalidPropertyException("Exception occurred while trying " + "to instantiate JavaBean '" + jbClassName + "':\n" + exc.toString());
472       }
473    }
474    return setterArgs;
475 }
476 
477   /** Instantiate the JavaBean, and set all of its properties
478    * The element must be of type "JavaBean"
479    * @param eJavaBean The DOM Element object representing the JavaBean.
480    * It is the root of a DOM document tree that contains the information
481    * used to instantiate and initalize the JavaBean. The element's
482    * tag must be JavaBean.
483    * @return java.lang.Object
484    * @exception IOException
485    * @exception ClassNotFoundException
486    * @exception IntrospectionException
487    */
488   protected static Object instantiateBean(Element eJavaBean)
489    throws IOException, ClassNotFoundException, IntrospectionException {
490 
491    // Load the value from the node
492    Class classOfBean = getPropertyType(eJavaBean, null);
493    Object jb = loadValue(classOfBean, eJavaBean, null);
494 
495    // Return the newly-instantiated JavaBean.
496    return jb;
497   }           
498 /** Convenience function that prints "null" or "not null"
499  * for an object
500  */
501 public static String isNull(Object o) {
502    // Added a comment
503    return ((o == null) ? "null" : "not null");
504   }  
505   /** Given an XML document element whose type is "JavaBean",
506    * return the value of its "CLASS" attribute. Return null
507    * if anything goes wrong
508    * @param eJavaBean The Element object containing a CLASS attribute
509    * @return The value of the Element's CLASS attribute.
510    */
511   protected static String JBClassName(Element eJavaBean) {
512    /// Get bean class name and create bean based on name
513    Attr jbClassAttr = eJavaBean.getAttributeNode("CLASS");
514    if (jbClassAttr == null)
515      return null;
516    String jbClassName = jbClassAttr.getValue();
517    return jbClassName;
518   }  
519 /**
520  * Use a string to set the value of "object". "object" must either have a property
521  * editor or be settable/creatable as a string.
522  * @return boolean
523  * @param object java.lang.Object
524  * @param value java.lang.String
525  */
526 protected final static Object loadValue(Class classOfValue, String value, PropertyDescriptor pd) {
527 
528    // Begin by getting the setter method
529    Method setter = pd.getWriteMethod();
530 
531    // The easiest way to handle this conversion is to use the
532    // property editor for the type. This is especially convenient
533    // because it handles all primitive types.
534    Class peClass = pd.getPropertyEditorClass();
535    PropertyEditor pe = null;
536 
537    // Create a property editor for this type, set it up to edit the
538    // object we're loading, then use setAsText() to set the value.
539    // This actually makes invoking the setter unnecessary.
540    if (peClass != null) {
541       try {
542          pe = (PropertyEditor) (peClass.newInstance());
543       } catch (Exception ex) {
544          ; // Ignore if we can't find it
545       }
546    }
547 
548    // Look for default if we haven't yet found one
549    if (pe == null) {
550       pe = PropertyEditorManager.findEditor(classOfValue);
551    }
552    if (pe != null) {
553       try {
554          pe.setAsText(value);
555          return pe.getValue();
556       } catch (Exception ex) {
557          Pe("loadValue(): error setting property " + pd.getName());
558       }
559    }
560 
561    // Nope, couldn't do it.
562    return null;
563 }
564 
565 /**
566  * Load a value from an element node. The resulting value will either be a
567  * JavaBean or a primitive wrapper object; in either case, the result is almost
568  * always passed to a setter method. If the PropertyDescriptor is not null,
569  * then this object is a property of another object, and the descriptor describes it.
570  * @return boolean
571  * @param bean java.lang.Object
572  * @param node org.w3c.dom.Node
573  */
574 public static Object loadValue(Class objectType, Element element, PropertyDescriptor pd) {
575    Object value = null;
576    Pe("Loading element " + element.getTagName() + " for object " + objectType.getName());
577 
578    // If this is a primitive property, we want to return an object
579    // of the appropriate type for the property descriptor's setter method
580    if (pd != null) {
581       if (objectType.isPrimitive()) {
582          value = getPrimitive(objectType, element);
583          return value;
584       }
585    }
586 
587    // Create an instance of this bean. This will be our return value.
588    try {
589       value = Beans.instantiate(null, objectType.getName());
590    } catch (Exception ex) {
591       return null;
592    }
593 
594    // First, find out if this object knows how to read itself
595    // from a DOM tree. If it does, we simply defer to the object
596    // itself, and we have no further work to do.
597    Method domSetter = getDOMSetter(value, null);
598    if (domSetter != null) {
599       try {
600          domSetter.invoke(value, new Object[] {element});
601       } catch (Exception ex) {
602          Pe("loadValue:");
603          Pe(ex.getClass().getName());
604          ex.printStackTrace();
605       }
606       return value;
607    }
608 
609    // Get the first child of the element that is either nonwhite text (in
610    // which case we try to initialize the value from that); or a noncomment
611    // nonterminal node, in which case the value must be a bean.
612    Node n = Util.getFirstInterestingChild(element);
613 
614    // If the syntax is ...,
615    // then we want to be looking under the  node for properties,
616    // not under the  node.
617    Element topElement = element;
618    if (n instanceof Element && ((Element)n).getTagName().equals("JavaBean")) {
619       topElement = (Element)n;
620    }
621 
622    // If n is null, initialize value with a blank string.
623    // If n is Text, initialize value with the value of the text.
624    String scalarValue = null;
625    if (n == null) {
626       scalarValue = "";
627    } else {
628       if (n instanceof Text) {
629          scalarValue = ((Text) n).getData();
630       }
631    }
632 
633    // If scalarValue is not null, then we want to set the value from
634    // a string. This makes it easy for objects to simply serialize
635    // themselves to flat strings, and then deserialize themselves
636    // back from those strings. Essentially, the PropertyEditor works
637    // as a tiny parser for the string value.
638    if (scalarValue != null && pd != null) {
639       // Use the property editor to initialize the value.
640       if ((value = loadValue(objectType, scalarValue, pd)) != null) {
641          return value;
642       }
643       Pe("loadValue() failed for property " + pd.getName() + ", value = '" + scalarValue + "'");
644    }
645 
646    // Since the object didn't know how to set itself from a DOM,
647    // and the DOM that represents it contains multiple nodes, we're
648    // going to have to enumerate all of the properties. Assume that
649    // each subnode is a property, creating and initializing a value for
650    // each one, and then using the property setter to set that value.
651    // If a  node exists in my subnodes, that's the list of
652    // my properties, instead of all of my subnodes.
653    Node propertyList = topElement;
654    NodeList nl = topElement.getChildNodes();
655    int i;
656    for (i = 0; i < nl.getLength(); i++) {
657       if (nl.item(i) instanceof Element) {
658          Element e = (Element) (nl.item(i));
659          if (e.getTagName().equals("Properties")) {
660             propertyList = e;
661             break;
662          }
663       }
664    }
665 
666    // Introspect object to get properties, then create a hash table
667    // with property names as keys and descriptors as contents
668    // Use the introspector to get the property descriptors
669    // for this bean.
670    PropertyDescriptor[] pds = getPropertyDescriptors(value.getClass());
671 
672    // Create a hash table of property names and property descriptors
673    Hashtable htpd = new Hashtable();
674    for (i = 0; i < pds.length; i++) {
675       htpd.put(pds[i].getName().toLowerCase(), pds[i]);
676    }
677 
678    // Now iterate through all of the properties in the propertyList,
679    // loading each one recursively, and passing the returned value
680    // to the setter method.
681    nl = propertyList.getChildNodes();
682    for (i = 0; i < nl.getLength(); i++) {
683 
684       // Get the name of the property, ignoring rubbish
685       String propertyName = getPropertyName(nl.item(i));
686       if (propertyName == null)
687          continue;
688 
689       p("Property name = '" + propertyName + "'");
690       
691       // Must be an element, since it wouldn't have a propertyName if it didn't
692       Element e = (Element) (nl.item(i));
693 
694       // Get the property descriptor for the property, ignoring
695       // if there's no descriptor for it
696       PropertyDescriptor thispd = (PropertyDescriptor) (htpd.get(propertyName.toLowerCase()));
697       if (thispd == null) {
698          Pe("Couldn't get PropertyDescriptor for property " + propertyName);
699          continue;
700       }
701 
702       // See if either the property descriptor or the bean knows how
703       // to set the value as a DOM tree
704       Method mDOMSetter = getDOMSetter(value, thispd);
705 
706       if (mDOMSetter != null) {
707          P(" DOM Setter = " + mDOMSetter.getName());
708          try {
709             mDOMSetter.invoke(value, new Object[] {e});
710             continue;
711          } catch (Exception ee) {
712             Pe("Exception occurred while setting " + propertyName + " as DOM: " + ee.getMessage());
713             ee.printStackTrace();
714             return null;
715          }
716       } else {
717          p(", no DOM setter");
718       }
719 
720       // Call the setter directly. To do this, we have to get the setter method for
721       // the property, create an instance of its property type, and
722       // try to recursively loadValue() a value for it.
723       // That should handle both properties that are JavaBeans
724       // and properties that can somehow be constructed from a string.
725       Method mSetter = thispd.getWriteMethod();
726       if (mSetter != null) {
727          P(", Setter " + mSetter.getName());
728          try {
729             // Get the class of the property
730             Class cValue = getPropertyType(e, thispd);
731             Object setterArg = null;
732 
733             // The setter argument is the object referred to by the
734             // current element e. Calling loadValue() recursively here
735             // returns the argument for the setter.
736             if ((setterArg = loadValue(cValue, e, thispd)) != null) {
737                mSetter.invoke(value, new Object[] {setterArg});
738             } else {
739                Pe("Couldn't set " + cValue.getName() + " " + thispd.getName());
740             }
741          } catch (Exception ex) {
742             Pe("Couldn't create or set " + thispd.getPropertyType().getName() + " for property " + pd.getName());
743             ex.printStackTrace();
744          }
745       } else {
746          P(", Setter null");
747       }
748    }
749 
750    // Return the value we've just created.
751    return value;
752 }
753 
754 /** Reads XML from a file, creates the corresponding JavaBean,
755    * and then prints the bean by calling its "print()" method (if
756    * it has one.)
757    */
758 public static void main(String args[]) {
759    try {
760       Object b = readXMLBean(args[0]);
761       try {
762          Method printer = b.getClass().getMethod("print", null);
763          P("--- Read a JavaBean of type " + b.getClass().getName());
764          P("===================================================================");
765          printer.invoke(b, null);
766       } catch (Exception ee) {
767          P("Unable to print JavaBean");
768       }
769    } catch (Exception ee) {
770 
771       P("Threw exception " + ee.getClass().getName());
772       P("Couldn't read JavaBean from file " + args[0]);
773       ee.printStackTrace();
774    }
775 }
776 
777 /** Prints a string to stdout. Convenience function to save typing.
778  */
779 public static void p(String s) {
780    System.out.print(s);
781 }
782 
783 /** Prints a string and newline to stdout. Convenience function to save typing.
784  */
785 public static void P(String s) {
786    System.out.println(s);
787 }
788 
789 /** Prints a string to stderr. Convenience function to save typing.
790  */
791 public static void pe(String s) {
792    System.err.print(s);
793 }
794 
795 /** Prints a string and newline to stderr. Convenience function to save typing.
796  */
797 public static void Pe(String s) {
798    System.err.println(s);
799 }
800 
801 /** Read a Bean's state from an XML file
802   * @param f The file from which to read the JavaBean's state.
803   * @return Object The newly-created, initialized JavaBean.
804   * @exception IOException
805   * @exception ClassNotFoundException
806   * @exception IntrospectionException
807   */
808 public static Object readXMLBean(File f)
809    throws IOException, ClassNotFoundException, IntrospectionException {
810    FileReader fr = new FileReader(f);
811    P("Created file reader");
812    Object o = readXMLBean(fr);
813    return o;
814 }
815 
816 /** Read a Bean's state from a character stream
817   * @param r The Reader from which to read the JavaBean's state.
818   * @return Object The newly-created, initialized JavaBean.
819   * @exception IOException
820   * @exception ClassNotFoundException
821   * @exception IntrospectionException
822   */
823   public static Object readXMLBean(Reader r)
824    throws IOException, ClassNotFoundException, IntrospectionException {
825 
826    // Read document from XML file
827    String sParserClassname = "";
828    Parser parser = null;
829 
830    // Create a SAX parser
831    try {
832       parser = ParserFactory.makeParser("com.ibm.xml.parsers.NonValidatingDOMParser");
833    } catch (Exception exc) {
834       System.err.println("Exception");
835       exc.printStackTrace();
836    }
837    
838    P("Created parser");
839 
840    // Run the SAX parser against the input stream
841    try {
842       parser.parse(new org.xml.sax.InputSource(r));
843    } catch (SAXException sx) {
844       System.err.println("Exception: " + sx.toString());
845       sx.printStackTrace();
846    } catch (Exception ex) {
847       System.err.println("Threw " + ex.getClass().getName());
848       ex.printStackTrace();
849    }
850    
851    // Since we know the SAX parser is also a DOM parser,
852    // we can ask it for its resulting document.
853    Document d = ((NonValidatingDOMParser)parser).getDocument();
854    P("Got document " + isNull(d));
855 
856    Element eJavaBean = d.getDocumentElement();
857    P("eJavaBean is " + isNull(eJavaBean));
858    
859    Object o = instantiateBean(eJavaBean);
860 
861    return o;
862   }    
863 /** Read a Bean's state from an XML file
864   * @param s The name of the file from which to read the JavaBean's state.
865   * @return Object The newly-created, initialized JavaBean.
866   * @exception IOException
867   * @exception ClassNotFoundException
868   * @exception IntrospectionException
869   */ 
870   public static Object readXMLBean(String s)
871    throws IOException, ClassNotFoundException, IntrospectionException {
872    return readXMLBean(new File(s));
873   }  
874 /**
875  * Set the primitive property of object , indicated by , to the contents
876  * of the first nonempty, noncomment subnode of 
877  * @return boolean
878  * @param value java.lang.Object
879  * @param node org.w3c.dom.Node
880  * @param pd java.beans.PropertyDescriptor
881  */
882 protected static boolean setPrimitive(Object value, Node node, PropertyDescriptor pd) {
883    Class propertyType = pd.getPropertyType();
884 
885    // If it's not primitive, we have nothing to do.
886    if (!propertyType.isPrimitive()) {
887       return false;
888    }
889 
890    // Likewise if there's no setter
891    Method mSetter = pd.getWriteMethod();
892    if (mSetter == null) {
893       return false;
894    }
895 
896    // Find first nonempty text node. Return false if there's anything here
897    // but comments or Text nodes.
898    int i = 0;
899    String stringValue = null;
900    NodeList nl = node.getChildNodes();
901    for (i = 0; i < nl.getLength(); i++) {
902       Node n = nl.item(i);
903 
904       // If it's a comment, skip
905       if (n instanceof Comment) {
906          continue;
907       }
908 
909       // If it's Text, but contains only white space, skip
910       if (n instanceof Text) {
911          String text = ((Text) n).getData();
912          if (text.trim().equals("")) {
913             continue;
914          } else {
915             stringValue = text;
916             break;
917          }
918       }
919 
920       // If it's anything but Text or a comment, there's a problem, because
921       // there's no sensible way to map a tree to a primitive type. So we
922       // complain and return false.
923       Pe("Tried to set property " + value.getClass().getName() + "." + pd.getName() + " to non-string");
924       return false;
925    }
926 
927    // If empty, we use empty string. Notice there's a problem here: how
928    // do we represent property values that are null? (Exercise for the reader)
929    if (stringValue == null) {
930       stringValue = "";
931    }
932 
933    try {
934 
935       // Get the argument list for the setter
936       Object setterArg = getPrimitiveSetterArg(propertyType, stringValue);
937 
938       // Call the setter with these arguments
939       mSetter.invoke(value, new Object[] { setterArg } );
940       return true;
941       
942    } catch (Exception ex) {
943       Pe("Calling property setter " + mSetter.getName() + " threw " + ex.getClass().getName());
944       ex.printStackTrace();
945    }
946 
947    // Failed if we got this far
948    return false;
949 }
950 
951 /**
952  * Try to set a property using a custom method that knows how
953  * to initialize a bean from a DOM tree.
954  * @return boolean
955  * @param jb java.lang.Object
956  * @param e org.w3c.dom.Element
957  * @param pd java.beans.PropertyDescriptor
958  */
959 protected static boolean setPropertyAsDOM(Object jb, Element e, PropertyDescriptor pd) 
960    throws IllegalAccessException, InvocationTargetException {
961 
962    String sName = pd.getName();
963 
964       // Let's define a "DOM property accessor" as a property accessor
965       // that sets or returns a JavaBean property as a DOM Element, instead
966       // of as its native type. The "DOM setter" will then have this signature:
967       //    void setter(Element e)
968       // and the "DOM getter" will have this signature:
969       //    Element getter()
970       // If a JavaBean specifies a DOM setter for a property, that method
971       // is used to set the property.
972       //
973       // There are two ways to specify a DOM setter
974       // 1. define a method
975       //         void setAsDOM(Element e)
976       // 2. return a XMLPropertyDescriptor whose getDOMWriteMethod()
977       //    returns a Method object that has the same signature as
978       //    the method in [1].
979       // The first option is easy (naming pattern), the second option is
980       // more flexible (since any method with the appropriate signature may
981       // be used.) Of getDOMWriteMethod() returns null, that means that
982       // there is no way to write that property.
983       Method domSetter = null;
984 
985       // If pd is an XMLPropertyDescriptor, get the DOMSetter
986       // method from pd, and call them method (assuming
987       // the method reference returned isn't null). Otherwise,
988       // check if a setPROPAsDom method exists
989       if (pd instanceof XMLPropertyDescriptor) {
990          XMLPropertyDescriptor xpd = (XMLPropertyDescriptor) pd;
991          domSetter = xpd.getDOMWriteMethod();
992       } else {
993          Class[] domSetterParams = {org.w3c.dom.Node.class};
994          String sDOMSetterName = "set" + Util.capitalize(sName) + "AsDOM";
995          try {
996             domSetter = jb.getClass().getMethod(sDOMSetterName, domSetterParams);
997          } catch (NoSuchMethodException ex) {
998             domSetter = null;
999          }
1000       }
1001 
1002       // If we found a DOM setter, invoke it, and we're done!
1003       if (domSetter != null) {
1004          Object[] domSetterArgs = { e };
1005          domSetter.invoke(jb, domSetterArgs);
1006          return true;
1007       }
1008    return false;
1009 }
1010 
1011 }
1012