KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jdom > contrib > beans > BeanMapper


1 /*--
2
3  $Id: BeanMapper.java,v 1.5 2004/12/11 00:06:40 jhunter Exp $
4
5  Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin & Alex Chaffee.
6  All rights reserved.
7
8  Redistribution and use in source and binary forms, with or without
9  modification, are permitted provided that the following conditions
10  are met:
11
12  1. Redistributions of source code must retain the above copyright
13     notice, this list of conditions, and the following disclaimer.
14
15  2. Redistributions in binary form must reproduce the above copyright
16     notice, this list of conditions, and the disclaimer that follows
17     these conditions in the documentation and/or other materials
18     provided with the distribution.
19
20  3. The name "JDOM" must not be used to endorse or promote products
21     derived from this software without prior written permission. For
22     written permission, please contact <request_AT_jdom_DOT_org>.
23
24  4. Products derived from this software may not be called "JDOM", nor
25     may "JDOM" appear in their name, without prior written permission
26     from the JDOM Project Management <request_AT_jdom_DOT_org>.
27
28  In addition, we request (but do not require) that you include in the
29  end-user documentation provided with the redistribution and/or in the
30  software itself an acknowledgement equivalent to the following:
31      "This product includes software developed by the
32       JDOM Project (http://www.jdom.org/)."
33  Alternatively, the acknowledgment may be graphical using the logos
34  available at http://www.jdom.org/images/logos.
35
36  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39  DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
40  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47  SUCH DAMAGE.
48
49  This software consists of voluntary contributions made by many
50  individuals on behalf of the JDOM Project and was originally
51  created by Jason Hunter <jhunter_AT_jdom_DOT_org> and
52  Brett McLaughlin <brett_AT_jdom_DOT_org>. For more information
53  on the JDOM Project, please see <http://www.jdom.org/>.
54
55  */

56
57 package org.jdom.contrib.beans;
58
59 import java.lang.reflect.*;
60 import java.io.File JavaDoc;
61 import java.io.IOException JavaDoc;
62 import java.io.PrintStream JavaDoc;
63 import java.util.Iterator JavaDoc;
64 import java.util.*;
65 import java.beans.*;
66 import org.jdom.Document;
67 import org.jdom.Element;
68 import org.jdom.Attribute;
69 import org.jdom.Namespace;
70 import org.jdom.JDOMException;
71 import org.jdom.input.SAXBuilder;
72 import org.jdom.output.XMLOutputter;
73
74 /**
75  * Maps a JavaBean to an XML tree and vice versa. (Yes, it's yet
76  * another XML Data Binding solution.) Given a JavaBean, it will
77  * produce a JDOM tree whose elements correspond to the bean's
78  * property values. Given a JDOM tree, it will return a new instance
79  * of that bean whose properties have been set using the corresponding
80  * values in the JDOM tree. <p>
81  *
82  * By default, it assumes each element maps to a property of the same
83  * name, subject to normal capitalization rules. That is, an element
84  * &lt;foo&gt; will map to the methods setFoo and getFoo. You can
85  * change this behavior by calling the various <code>addMapping</code>
86  * methods. For instance, to map a an element &lt;date&gt; to the
87  * property "birthDate" (using methods setBirthDate and getBirthDate),
88  * call <pre>mapper.addMapping("birthDate", "date");</pre> You can
89  * also map a property to an attribute, either of a child or of the
90  * parent (see JavaDoc for
91  * <a HREF="#addMapping(java.lang.String, java.lang.String, java.lang.String)">
92  * addMapping(String property, String element, String attribute)</a>
93  * for details). <p>
94  *
95  * During Bean -> JDOM conversion, if a BeanInfo object is found, it
96  * will be respected. See JavaDoc for java.beans.Introspector. <p>
97  *
98  * If a given property, element, or attribute is to be skipped
99  * (ignored) during conversion, call the appropriate ignore method
100  * (ignoreProperty, ignoreElement, or ignoreAttribute). This is also
101  * appropriate if your bean has multiple accessors (properties) for
102  * the same underlying data. <p>
103  *
104  * Support for Namespaces is rudimentary at best and has not been
105  * well-tested; if you specify a Namespace then all created elements
106  * and attributes will be in that namespace (or all elements not in
107  * that namespace will be skipped, for JDOM->Bean mapping). <p>
108  *
109  * If a bean property type is a Java array, its items will be mapped
110  * to multiple child elements of the bean element (multiple children
111  * at the base level, not one child). This is to provide an easier
112  * transition for XML documents whose elements contain multiple
113  * children with the same name.<p>
114  *
115  * Please try this out on your own beans. If there is a case that
116  * fails to do what you like (for instance, properties with custom
117  * class types), let me know and I'll try to work it out. <p>
118  *
119  * <pre>TODO:
120  * support list properties (collections other than Java arrays)
121  * allow lists/arrays to map to either scattered elements or a single nested element
122  * sort XML elements in some order other than random BeanInfo order
123  * support for BeanInfoSearchPaths
124  * multiple packages searched for beans
125  * better support for Namespaces (hard)
126  * allow stringconverter to map object -> string (not just string -> object)
127  * id/idref support for cyclical references
128  * more type converters
129  * custom property converters
130  * known issue: inner class bean can't be found jdom->bean (workaround: define mapping containing class name)
131  * </pre>
132  *
133  * <h2>Example:</h2>
134  * <h3>TestBean</h3>
135  * <pre>
136  * public class TestBean implements java.io.Serializable {
137  * public String getName() { ... }
138  * public int getAge() { ... }
139  * public Date getBirthdate() { ... }
140  * public TestBean getFriend() { ... }
141  * public void setName(String name) { ... }
142  * public void setAge(int age) { ... }
143  * public void setBirthdate(Date birthdate) { ... }
144  * public void setFriend(TestBean friend) { ... }
145  * public String toString() { ... }
146  * }
147  * </pre>
148  * <h3>XML Representation</h3>
149  * <pre>&nbsp;&lt;testBean&gt;
150  * &nbsp;&nbsp;&lt;dob age="31"&gt;Fri Aug 08 00:00:00 EDT 1969&lt;/dob&gt;
151  * &nbsp;&nbsp;&lt;name&gt;Alex&lt;/name&gt;
152  * &nbsp;&nbsp;&lt;friend&gt;
153  * &nbsp;&nbsp;&nbsp;&nbsp;&lt;testBean&gt;
154  * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;dob age="25"&gt;Thu May 01 00:00:00 EDT 1975&lt;/dob&gt;
155  * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;name&gt;Amy&lt;/name&gt;
156  * &nbsp;&nbsp;&nbsp;&nbsp;&lt;/testBean&gt;
157  * &nbsp;&nbsp;&lt;/friend&gt;
158  * &lt;/testBean&gt;
159  * </pre>
160  * <h3>Mapping code</h3>
161  * <pre> BeanMapper mapper = new BeanMapper();
162  * mapper.addMapping("birthdate", "dob"); // element mapping
163  * mapper.addMapping("age", "dob", "age"); // attribute mapping
164  * mapper.setBeanPackage("org.jdom.contrib.beans");
165  * </pre>
166  * <h3>Converting Bean to JDOM</h3>
167  * <pre>Document doc = mapper.toDocument(alex);</pre>
168  * <h3>Converting JDOM to Bean</h3>
169  * <pre>TestBean alex = mapper.toBean(doc);</pre>
170  *
171  * @author Alex Chaffee (alex@jguru.com)
172  **/

173
174 public class BeanMapper {
175
176     protected String JavaDoc beanPackage;
177     protected Namespace namespace;
178     protected boolean ignoreMissingProperties = false;
179     protected boolean ignoreNullProperties = true;
180     protected List mappings = new ArrayList();
181     protected StringConverter stringconverter = new StringConverter();
182     
183     /**
184      * Default constructor. If you are only doing bean -> XML
185      * mapping, you may use the mapper immediately. Otherwise, you
186      * must call setBeanPackage.
187      **/

188     public BeanMapper()
189     {
190     }
191     
192     // Properties
193

194     /**
195      * @param beanPackage the name of the package in which to find the
196      * JavaBean classes to instantiate
197      **/

198     public void setBeanPackage(String JavaDoc beanPackage)
199     {
200         this.beanPackage = beanPackage;
201     }
202     
203     /**
204      * Use this namespace when creating the XML element and all
205      * child elements.
206      **/

207     public void setNamespace(Namespace namespace)
208     {
209         this.namespace = namespace;
210     }
211
212     /**
213      * Get the object responsible for converting a string to a known
214      * type. Once you get this, you can call its setFactory() method
215      * to add a converter for a custom type (for which toString() does
216      * not suffice). The default string converter has a factory that
217      * recognizes several date formats (including ISO8601 -
218      * e.g. "2000-09-18 18:51:22-0600" or some substring thereof).
219      **/

220     public StringConverter getStringConverter() {
221         return stringconverter;
222     }
223
224     /**
225      * Set a custom string converter.
226      **/

227     public void setStringConverter(StringConverter stringconverter) {
228         this.stringconverter = stringconverter;
229     }
230         
231     /**
232      * In mapping from Bean->JDOM, if we encounter an property with a
233      * null value, should
234      * we ignore it or add an empty child element/attribute (default: true)?
235      * @param b true = ignore, false = empty element
236      **/

237     public void setIgnoreNullProperties(boolean b) {
238         ignoreNullProperties = b;
239     }
240
241     /**
242      * In mapping from JDOM->Bean, if we encounter an element or
243      * attribute without a corresponding property in the bean, should
244      * we ignore it or throw an exception (default: false)?
245      * @param b true = ignore, false = throw exception
246      **/

247     public void setIgnoreMissingProperties(boolean b) {
248         ignoreMissingProperties = b;
249     }
250
251
252     // Bean -> JDOM Mapping
253

254     /**
255      * Converts the given bean to a JDOM Document.
256      * @param bean the bean from which to extract values
257      **/

258     public Document toDocument(Object JavaDoc bean) throws BeanMapperException {
259         return toDocument(bean, null);
260     }
261
262     /**
263      * Converts the given bean to a JDOM Document.
264      * @param bean the bean from which to extract values
265      * @param name the name of the root element (null => use bean class name)
266      **/

267     public Document toDocument(Object JavaDoc bean, String JavaDoc elementName)
268                       throws BeanMapperException {
269         Element root = toElement(bean, elementName);
270         Document doc = new Document(root);
271         return doc;
272     }
273
274     /**
275      * Converts the given bean to a JDOM Element.
276      * @param bean the bean from which to extract values
277      * @param elementName the name of the element (null => use bean class name)
278      **/

279     public Element toElement(Object JavaDoc bean) throws BeanMapperException
280     {
281         return toElement(bean, null);
282     }
283     
284     /**
285      * Converts the given bean to a JDOM Element.
286      * @param bean the bean from which to extract values
287      * @param elementName the name of the element (null => use bean class name)
288      **/

289     public Element toElement(Object JavaDoc bean, String JavaDoc elementName)
290                      throws BeanMapperException {
291         BeanInfo info;
292         try {
293             // cache this?
294
info = Introspector.getBeanInfo(bean.getClass());
295         }
296         catch (IntrospectionException e) {
297             throw new BeanMapperException("Mapping bean " + bean, e);
298         }
299         
300         // create element
301
Element element;
302         String JavaDoc beanname;
303         if (elementName != null) {
304             element = createElement(elementName);
305         }
306         else {
307             Class JavaDoc beanclass = info.getBeanDescriptor().getBeanClass();
308             beanname = unpackage(beanclass.getName());
309             element = createElement(beanname);
310         }
311         
312         // get all properties, set as child-elements
313
PropertyDescriptor[] properties = info.getPropertyDescriptors();
314         for (int i=0; i<properties.length; ++i) {
315             PropertyDescriptor prop = properties[i];
316             String JavaDoc propertyName = prop.getName();
317
318             Method method = prop.getReadMethod();
319
320             // hack to skip Object.getClass
321
if (method.getName().equals("getClass") &&
322                 prop.getPropertyType().getName().equals("java.lang.Class"))
323                 continue;
324
325             // skip ignored properties
326
if (isIgnoredProperty(propertyName))
327                 continue;
328             
329             // do we have a mapping for this property?
330
Mapping mapping = getMappingForProperty(propertyName);
331
332             // get the value
333
if (method.getParameterTypes().length != 0)
334                 // if this getter takes parameters, ignore it
335
continue;
336
337             Object JavaDoc valueObject = null;
338             try {
339                 Object JavaDoc[] args = new Object JavaDoc[0]; // make static?
340
valueObject = method.invoke(bean, args);
341             }
342             catch (java.lang.IllegalAccessException JavaDoc e) {
343                 throw new BeanMapperException("Mapping " + propertyName, e);
344             }
345             catch (java.lang.reflect.InvocationTargetException JavaDoc e) {
346                 throw new BeanMapperException("Mapping " + propertyName, e);
347             }
348
349             // convert it to a string or element or list
350
Object JavaDoc value = convertValue(valueObject);
351
352             if (value == null && ignoreNullProperties) {
353                 debug("Ignoring null " + propertyName);
354                 continue;
355             }
356             
357             String JavaDoc childElementName;
358             
359             if (mapping == null) {
360                 childElementName = propertyName;
361             }
362             else
363                 childElementName = mapping.element;
364             
365             // get existing element, or create it.
366

367             // we must look for an existing element even when setting
368
// an element value, since it may have been subject to an
369
// attribute mapping, and thus already created (without
370
// any content, but with an attribute).
371

372             Element child = null;
373             if (childElementName != null) {
374                 if (namespace != null)
375                     child = element.getChild(childElementName, namespace);
376                 else {
377                     child = element.getChild(childElementName);
378                 }
379                 if (child == null) {
380                     child = createElement(childElementName);
381                     element.addContent(child);
382                 }
383             }
384
385             if (mapping == null || mapping.attribute == null) {
386                 // set as element
387
setElementValue(propertyName, childElementName,
388                                 element, child, value);
389             }
390             
391             else {
392                 try {
393                     // set as attribute
394
if (value == null)
395                         value = ""; // no such thing as null attribute in XML
396
if (childElementName == null) // add attr to parent
397
element.setAttribute(mapping.attribute, (String JavaDoc)value);
398                     else // add attr to child
399
child.setAttribute(mapping.attribute, (String JavaDoc)value);
400                 }
401                 catch (ClassCastException JavaDoc e) {
402                     throw new BeanMapperException(
403                       "Can't set type " + value.getClass() + " as attribute");
404                 }
405             }
406         }
407         
408         // todo: sort in mapping order
409
return element;
410     } // toElement
411

412     /**
413      * convert property value (returned from getter) to a string or
414      * element or list
415      **/

416     protected Object JavaDoc convertValue(Object JavaDoc value) throws BeanMapperException
417     {
418         if (value == null)
419             return null;
420         Object JavaDoc result;
421         Class JavaDoc type = value.getClass();
422         String JavaDoc classname = type.getName();
423
424         // todo: allow per-type callback to convert (if toString() is
425
// inadequate) -- extend stringconverter?
426
if (classname.startsWith("java.lang.") ||
427             classname.equals("java.util.Date")
428             )
429         {
430             result = value.toString();
431         }
432         else if (type.isArray()) {
433             // it's an array - use java.lang.reflect.Array to extract
434
// items (or wrappers thereof)
435
List list = new ArrayList();
436             for (int i=0; i<Array.getLength(value); ++i) {
437                 Object JavaDoc item = Array.get(value, i);
438                 list.add( convertValue(item) ); // recurse
439
}
440             result = list;
441         }
442         else
443         {
444             // treat it like a bean
445
// recursive call to mapper
446
debug("Recurse on bean " + classname + "=" + value);
447             result = toElement(value);
448         }
449
450         return result;
451     } // convertValue
452

453     protected void setElementValue(String JavaDoc propertyName,
454                                    String JavaDoc elementName,
455                                    Element parent,
456                                    Element child,
457                                    Object JavaDoc value) throws BeanMapperException {
458         debug("setElementValue(" + propertyName + "," +
459                            elementName + "," +
460                            child.getName() + "," +
461                            value + ")");
462                            
463         if (value == null)
464             ; // do nothing
465
else if (value instanceof Element) {
466             child.addContent((Element)value);
467         }
468         else if (value instanceof String JavaDoc) {
469             child.setText((String JavaDoc)value);
470         }
471         else if (value instanceof List) {
472             for (Iterator JavaDoc it = ((List)value).iterator();
473                  it.hasNext(); )
474             {
475                 Object JavaDoc item = it.next();
476                 if (child == null) {
477                     child = createElement(elementName);
478                     parent.addContent(child);
479                 }
480                 setElementValue(propertyName, elementName, parent, child, item);
481                 // this'll be weird if it's an array of arrays
482
child = null;
483             }
484         }
485         else
486             throw new BeanMapperException(
487             "Unknown result type for property " + propertyName + ": " + value);
488     }
489     
490     // JDOM -> Bean
491

492     /**
493      * Converts the given JDOM Document to a bean
494      * @param document the document from which to extract values
495      **/

496     public Object JavaDoc toBean(Document document) throws BeanMapperException {
497         return toBean(document.getRootElement());
498     }
499
500     public Object JavaDoc toBean(Element element) throws BeanMapperException {
501
502         Object JavaDoc bean = instantiateBean(element.getName());
503         
504         Iterator JavaDoc i;
505         Mapping mapping;
506         String JavaDoc propertyName;
507         List attributes;
508         List children;
509
510         Set alreadySet = new HashSet();
511         
512         // map Attributes of parent first
513
attributes = element.getAttributes();
514         for (i=attributes.iterator(); i.hasNext(); ) {
515             Attribute attribute = (Attribute)i.next();
516             debug("Mapping " + attribute);
517             mapping = getMappingForAttribute(null, attribute.getName());
518             propertyName = (mapping==null) ?
519                             attribute.getName() : mapping.property;
520             setProperty(bean, propertyName, attribute.getValue());
521         }
522
523         // map child Elements
524
children = element.getChildren();
525         debug(element.toString() + " has " + children.size() + " children");
526         for (i=children.iterator(); i.hasNext(); ) {
527             Element child = (Element)i.next();
528             debug("Mapping " + child);
529
530             mapping = getMappingForElement(child.getName());
531             propertyName = (mapping==null) ? child.getName() : mapping.property;
532             
533             // set bean property from element
534
PropertyDescriptor property =
535                 findPropertyDescriptor(bean, propertyName);
536             if (property != null) {
537                 if (!alreadySet.contains(child.getName())) {
538                     if (property.getPropertyType().isArray())
539                         setProperty(bean, property, element, child);
540                     else
541                         setProperty(bean, property, element, child);
542                 }
543             }
544
545             // Now map all attributes of this child
546
attributes = child.getAttributes();
547             for (Iterator JavaDoc iatt=attributes.iterator(); iatt.hasNext(); ) {
548                 Attribute attribute = (Attribute)iatt.next();
549                 debug("Mapping " + attribute);
550                 mapping = getMappingForAttribute(child.getName(),
551                                                  attribute.getName());
552                 propertyName = (mapping==null) ?
553                                 attribute.getName() : mapping.property;
554                 setProperty(bean, propertyName, attribute.getValue());
555             } // for attributes
556

557             alreadySet.add(child.getName());
558             
559         } // for children
560

561         return bean;
562     } // toBean
563

564
565     /**
566      * return a fresh new object of the appropriate bean type for
567      * the given element name.
568      * @return the bean
569      **/

570     protected Object JavaDoc instantiateBean(String JavaDoc elementName)
571                                 throws BeanMapperException {
572         // todo: search multiple packages
573
String JavaDoc className = null;
574         Class JavaDoc beanClass;
575         try {
576             Mapping mapping = getMappingForElement(elementName);
577             if (mapping != null &&
578                 mapping.type != null) {
579                 beanClass = mapping.type;
580             }
581             else {
582                 className = getBeanClassName(beanPackage, elementName);
583                 beanClass = Class.forName(className);
584             }
585             Object JavaDoc bean = beanClass.newInstance();
586             return bean;
587         }
588         catch (ClassNotFoundException JavaDoc e) {
589             throw new BeanMapperException("Class " + className +
590                 " not found instantiating " + elementName +
591                 " - maybe you need to add a mapping, or add a bean package", e);
592         }
593         catch (Exception JavaDoc e) {
594             throw new BeanMapperException("Instantiating " + elementName, e);
595         }
596     }
597
598     protected String JavaDoc getBeanClassName(String JavaDoc beanPackage, String JavaDoc elementName) {
599         return (beanPackage == null ? "" : (beanPackage + ".")) +
600             Character.toUpperCase(elementName.charAt(0)) +
601             elementName.substring(1);
602     }
603
604     protected PropertyDescriptor findPropertyDescriptor(Object JavaDoc bean,
605                                                         String JavaDoc propertyName)
606                                      throws BeanMapperException {
607         try {
608             // cache this?
609
BeanInfo info = Introspector.getBeanInfo(bean.getClass());
610             PropertyDescriptor[] properties = info.getPropertyDescriptors();
611             for (int i=0; i<properties.length; ++i) {
612                 PropertyDescriptor prop = properties[i];
613                 if (prop.getName().equals(propertyName))
614                     return prop;
615             }
616         }
617         catch (Exception JavaDoc e) {
618             throw new BeanMapperException("Finding property " + propertyName +
619                                           " for bean " + bean.getClass(), e);
620         }
621         if (ignoreMissingProperties) {
622             return null;
623         }
624         else {
625             throw new BeanMapperException("Missing property: " +
626                 propertyName + " in bean " + bean.getClass() + ": " + bean);
627         }
628     } // findPropertyDescriptor
629

630
631     /**
632      * set a property in the bean
633      * @param bean the bean to modify
634      * @param propertyName the name of the property to set
635      * @param value the new value
636      * @return true if successful, false if property not found
637      **/

638     protected boolean setProperty(Object JavaDoc bean, String JavaDoc propertyName,
639                                   Object JavaDoc value) throws BeanMapperException {
640         return setProperty(bean, findPropertyDescriptor(bean, propertyName),
641                            null, value);
642     }
643
644     /**
645      * set a property in the bean
646      * @param bean the bean to modify
647      * @param property the property to set
648      * @param value the new value
649      * @return true if successful, false if property not found
650      **/

651     protected boolean setProperty(Object JavaDoc bean, PropertyDescriptor property,
652                                   Element parent, Object JavaDoc value)
653                          throws BeanMapperException {
654         debug("setProperty: bean=" + bean + " property=" +
655                property.getName() + " value=" + value);
656         try {
657             if (property == null)
658                 return false;
659
660             // convert the value to the right type
661
Object JavaDoc valueObject;
662             if (property.getPropertyType().isArray()) {
663                 // build array based on children of this name
664
Element child = (Element)value;
665                 List children = parent.getChildren(child.getName());
666                 valueObject = buildArray(property, children);
667             }
668             else {
669                 // normal property
670
valueObject = convertJDOMValue(value,
671                                                property.getPropertyType());
672             }
673
674             // find and invoke the setter
675
String JavaDoc propertyName = property.getName();
676             Method setter = property.getWriteMethod();
677             Class JavaDoc[] params = setter.getParameterTypes();
678             if (params.length > 1)
679                 throw new BeanMapperException(
680                     "Setter takes multiple parameters: " + bean.getClass() +
681                     "." + setter.getName());
682
683             Class JavaDoc param = params[0];
684             if (param != property.getPropertyType())
685                 debug("Weird: setter takes " + param + ", property is " +
686                        property.getPropertyType());
687
688             debug("Invoking setter: " + setter.getName() +
689                   "(" + valueObject + ")");
690             setter.invoke(bean, new Object JavaDoc[] { valueObject });
691
692             return true;
693         }
694         catch (BeanMapperException e) {
695             throw e;
696         }
697         catch (Exception JavaDoc e) {
698             throw new BeanMapperException("Setting property " +
699                 property.getName() + "=" + value + " in " + bean.getClass(), e);
700         }
701     }
702
703     protected Object JavaDoc convertString(String JavaDoc value, Class JavaDoc type) {
704         if (value == null)
705             return null;
706         if (type == String JavaDoc.class)
707             return value;
708         else
709             return
710                 stringconverter.parse((String JavaDoc)value, type);
711     }
712
713     protected Object JavaDoc convertJDOMValue(Object JavaDoc value, Class JavaDoc type)
714                             throws BeanMapperException {
715         Object JavaDoc valueObject;
716                 
717         // Null value
718
if (value == null)
719             valueObject = null;
720
721         // String value
722
else if (value instanceof String JavaDoc) {
723             valueObject = convertString((String JavaDoc)value, type);
724         }
725
726         // Element value
727
else if (value instanceof Element) {
728             Element element = (Element)value;
729
730             // if the setter actually takes a JDOM element, pass it
731
if (type == Element.class)
732                 valueObject = value;
733
734             // no children, must be a text node
735
else if (element.getChildren().isEmpty())
736             {
737                 valueObject = convertString(element.getText(), type);
738             }
739
740             // we have to convert it into a bean
741
else if (element.getChildren().size() == 1) {
742                     
743                 // Make a recursive call to this BeanMapper to map
744
// the child element
745

746                 // Note that toBean could return a subclass of the
747
// property type, so just let it figure out the
748
// right type
749

750                 valueObject = toBean((Element)element.getChildren().get(0));
751             }
752             else {
753                 // element with multiple children -- must be an
754
// array property
755
throw new BeanMapperException(
756                     "Mapping of multiple child elements not implemented for " +
757                     element.getName());
758             }
759         }
760         else {
761             throw new BeanMapperException("Can't map unknown type: " +
762                                           value.getClass() + "=" + value);
763         }
764         return valueObject;
765     } // convert JDOMValue
766

767     /**
768      * @return an array of the appropriate type
769      **/

770     protected Object JavaDoc buildArray(PropertyDescriptor property, List children)
771                           throws BeanMapperException {
772         Class JavaDoc arrayClass = property.getPropertyType();
773
774         Class JavaDoc itemClass;
775         itemClass = arrayClass.getComponentType();
776
777         if (itemClass == null) {
778             throw new BeanMapperException("Can't instantiate array of type " +
779             arrayClass);
780         }
781         
782         // use java.lang.reflect.Array
783
Object JavaDoc array = Array.newInstance(itemClass, children.size());
784
785         // fill it
786
for (int i = 0; i<children.size(); ++i) {
787             Element child = (Element)children.get(i);
788             Object JavaDoc value = convertJDOMValue(child, itemClass);
789             debug( itemClass + "[" + i + "]=" + value );
790             Array.set(array, i, value);
791         }
792
793         return array;
794     }
795
796
797     public static Class JavaDoc getArrayItemClass(Class JavaDoc arrayClass)
798                                 throws ClassNotFoundException JavaDoc {
799         Class JavaDoc itemClass;
800         
801         // there seems to be no way to get the item class from an
802
// array class object, so parse the name
803
String JavaDoc arrayClassName = arrayClass.getName();
804         debug("Parsing array classname: " + arrayClassName);
805         if (arrayClassName.equals("[B")) {
806             itemClass = byte.class;
807         }
808         else if (arrayClassName.equals("[C")) {
809             itemClass = char.class;
810         }
811         else if (arrayClassName.equals("[D")) {
812             itemClass = double.class;
813         }
814         else if (arrayClassName.equals("[F")) {
815             itemClass = float.class;
816         }
817         else if (arrayClassName.equals("[I")) {
818             itemClass = int.class;
819         }
820         else if (arrayClassName.equals("[J")) {
821             itemClass = long.class;
822         }
823         else if (arrayClassName.equals("[S")) {
824             itemClass = short.class;
825         }
826         else if (arrayClassName.equals("[Z")) {
827             itemClass = boolean.class;
828         }
829         else if (arrayClassName.startsWith("[L")) {
830             itemClass = Class.forName(
831                 arrayClassName.substring(2, arrayClassName.length()-1));
832         }
833         else
834             itemClass = null;
835         return itemClass;
836     }
837
838     // Mappings
839

840     /**
841      * Map a property name to an XML element
842      * @param property the name of the property
843      * @param element the name of the element
844      **/

845     public void addMapping(String JavaDoc property, String JavaDoc element) {
846         addMapping(new Mapping(property, null, element, null));
847     }
848
849     /**
850      * Map a property name to an attribute in an XML element. If
851      * element is null, it's an attribute on the parent element
852      * (e.g. &lt;myBean id="123"&gt;); otherwise, it's an
853      * attribute on a child element (e.g. &lt;myBean&gt;&lt;thing
854      * id="123"&gt;).
855      * @param property the name of the property
856      * @param element the name of the element containing the attribute.
857      * null => parent element
858      * @param attribute the name of the attribute. null => set as element
859      **/

860     public void addMapping(String JavaDoc property, String JavaDoc element, String JavaDoc attribute) {
861         addMapping(new Mapping(property, null, element, attribute));
862     }
863
864     /**
865      * Map a property name to an element or an attribute. Can also
866      * specify which bean class to instantiate (applies to JDOM->Bean
867      * mapping).
868      *
869      * @param property the name of the property. null => look for property
870      * with same name as the element
871      * @param type Always convert this element name to this class.
872      * null => look for a bean with the same name as the element
873      * @param element the name of the element containing the attribute.
874      * null => parent element
875      * @param attribute the name of the attribute. null => set as element
876      **/

877     public void addMapping(String JavaDoc property, Class JavaDoc type,
878                            String JavaDoc element, String JavaDoc attribute) {
879         addMapping(new Mapping(property, type, element, attribute));
880     }
881     
882     public void addMapping(Mapping mapping) {
883         mappings.add(mapping);
884     }
885
886     public Mapping getMappingForProperty(String JavaDoc property) {
887         Iterator JavaDoc i = mappings.iterator();
888         while (i.hasNext()) {
889             Mapping m = (Mapping)i.next();
890             if (m.property != null && m.property.equals(property)) {
891                 return m;
892             }
893         }
894         return null;
895     }
896     
897     public Mapping getMappingForElement(String JavaDoc element) {
898         Iterator JavaDoc i = mappings.iterator();
899         while (i.hasNext()) {
900             Mapping m = (Mapping)i.next();
901             if (m.element.equals(element)) {
902                 return m;
903             }
904         }
905         return null;
906     }
907     
908     public Mapping getMappingForAttribute(String JavaDoc element, String JavaDoc attribute) {
909         Iterator JavaDoc i = mappings.iterator();
910         while (i.hasNext()) {
911             Mapping m = (Mapping)i.next();
912             if (m.element != null &&
913                 m.attribute != null &&
914                 m.element.equals(element) &&
915                 m.attribute.equals(attribute))
916             {
917                 return m;
918             }
919         }
920         return null;
921     }
922     
923     public class Mapping {
924         public String JavaDoc property;
925         public Class JavaDoc type;
926         public String JavaDoc element;
927         public String JavaDoc attribute;
928
929         /**
930          * @param property the name of the property. null => look for
931          * property with same name as the element
932          * @param type Always convert this element name to this class.
933          * null => look for a bean with the same name as the element
934          * @param element the name of the element containing the attribute.
935          * null => parent element
936          * @param attribute the name of the attribute. null => set as element
937          **/

938         public Mapping(String JavaDoc property, Class JavaDoc type,
939                        String JavaDoc element, String JavaDoc attribute) {
940             this.property = property;
941             this.type = type;
942             this.element = element;
943             this.attribute = attribute;
944         }
945         
946
947     }
948
949     // Hiding
950

951     protected Set ignoredProperties = new HashSet();
952     protected Set ignoredElements = new HashSet();
953     protected Set ignoredAttributes = new HashSet();
954     
955     public void ignoreProperty(String JavaDoc property) {
956         ignoredProperties.add(property);
957     }
958
959     public boolean isIgnoredProperty(String JavaDoc property) {
960         return ignoredProperties.contains(property);
961     }
962     
963     public void ignoreElement(String JavaDoc element) {
964         ignoredElements.add(element);
965     }
966     
967     public boolean isIgnoredElement(String JavaDoc element) {
968         return ignoredElements.contains(element);
969     }
970
971     static protected String JavaDoc toAttributeString(String JavaDoc element,
972                                               String JavaDoc attribute) {
973         return (element == null ? "." : element) +
974             "/@" + attribute;
975     }
976     
977     public void ignoreAttribute(String JavaDoc element, String JavaDoc attribute) {
978         ignoredAttributes.add(toAttributeString(element, attribute));
979     }
980     
981     public boolean isIgnoredAttribute(String JavaDoc element, String JavaDoc attribute) {
982         return ignoredAttributes.contains(
983                    toAttributeString(element, attribute));
984     }
985     
986     // Utilities
987

988     protected Element createElement(String JavaDoc elementName) {
989         return namespace == null ? new Element(elementName) :
990             new Element(elementName, namespace);
991     }
992     
993     protected static String JavaDoc unpackage(String JavaDoc classname) {
994         int dot = Math.max(classname.lastIndexOf("."),
995                            classname.lastIndexOf("$"));
996         if (dot > -1) {
997             classname = classname.substring(dot+1);
998         }
999         classname = Introspector.decapitalize(classname);
1000        return classname;
1001    }
1002
1003    public static int debug = 0;
1004    protected static void debug(String JavaDoc msg) {
1005        if (debug > 0)
1006            System.err.println("BeanMapper: " + msg);
1007    }
1008}
1009
1010
Popular Tags