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