1 package org.apache.commons.betwixt;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 import java.beans.BeanDescriptor;
20 import java.beans.BeanInfo;
21 import java.beans.IntrospectionException;
22 import java.beans.Introspector;
23 import java.beans.PropertyDescriptor;
24 import java.net.URL;
25 import java.util.ArrayList;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Map;
29
30 import org.apache.commons.beanutils.DynaBean;
31 import org.apache.commons.beanutils.DynaClass;
32 import org.apache.commons.beanutils.DynaProperty;
33 import org.apache.commons.betwixt.digester.XMLBeanInfoDigester;
34 import org.apache.commons.betwixt.digester.XMLIntrospectorHelper;
35 import org.apache.commons.betwixt.expression.EmptyExpression;
36 import org.apache.commons.betwixt.expression.Expression;
37 import org.apache.commons.betwixt.expression.IteratorExpression;
38 import org.apache.commons.betwixt.expression.StringExpression;
39 import org.apache.commons.betwixt.expression.Updater;
40 import org.apache.commons.betwixt.registry.DefaultXMLBeanInfoRegistry;
41 import org.apache.commons.betwixt.registry.XMLBeanInfoRegistry;
42 import org.apache.commons.betwixt.strategy.ClassNormalizer;
43 import org.apache.commons.betwixt.strategy.DefaultNameMapper;
44 import org.apache.commons.betwixt.strategy.DefaultPluralStemmer;
45 import org.apache.commons.betwixt.strategy.NameMapper;
46 import org.apache.commons.betwixt.strategy.PluralStemmer;
47 import org.apache.commons.logging.Log;
48 import org.apache.commons.logging.LogFactory;
49
50 /***
51 * <p><code>XMLIntrospector</code> an introspector of beans to create a
52 * XMLBeanInfo instance.</p>
53 *
54 * <p>By default, <code>XMLBeanInfo</code> caching is switched on.
55 * This means that the first time that a request is made for a <code>XMLBeanInfo</code>
56 * for a particular class, the <code>XMLBeanInfo</code> is cached.
57 * Later requests for the same class will return the cached value.</p>
58 *
59 * <p>Note :</p>
60 * <p>This class makes use of the <code>java.bean.Introspector</code>
61 * class, which contains a BeanInfoSearchPath. To make sure betwixt can
62 * do his work correctly, this searchpath is completely ignored during
63 * processing. The original values will be restored after processing finished
64 * </p>
65 *
66 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
67 * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
68 * @version $Id: XMLIntrospector.java,v 1.28.2.1 2004/06/19 16:24:09 rdonkin Exp $
69 */
70 public class XMLIntrospector {
71
72 /*** Log used for logging (Doh!) */
73 protected Log log = LogFactory.getLog( XMLIntrospector.class );
74
75 /*** should attributes or elements be used for primitive types */
76 private boolean attributesForPrimitives = false;
77
78 /*** should we wrap collections in an extra element? */
79 private boolean wrapCollectionsInElement = true;
80
81 /*** Maps classes to <code>XMLBeanInfo</code>'s */
82 private XMLBeanInfoRegistry registry = new DefaultXMLBeanInfoRegistry();
83
84 /*** Digester used to parse the XML descriptor files */
85 private XMLBeanInfoDigester digester;
86
87
88
89 /*** The strategy used to detect matching singular and plural properties */
90 private PluralStemmer pluralStemmer;
91
92 /*** The strategy used to convert bean type names into element names */
93 private NameMapper elementNameMapper;
94
95 /*** Strategy normalizes the Class of the Object before introspection */
96 private ClassNormalizer classNormalizer = new ClassNormalizer();
97
98 /***
99 * The strategy used to convert bean type names into attribute names
100 * It will default to the normal nameMapper.
101 */
102 private NameMapper attributeNameMapper;
103 /*** Should the existing bean info search path for java.reflect.Introspector be used? */
104 private boolean useBeanInfoSearchPath = false;
105
106 /*** Base constructor */
107 public XMLIntrospector() {
108 }
109
110 /***
111 * <p>Gets the current logging implementation. </p>
112 * @return the Log implementation which this class logs to
113 */
114 public Log getLog() {
115 return log;
116 }
117
118 /***
119 * <p>Sets the current logging implementation.</p>
120 * @param log the Log implementation to use for logging
121 */
122 public void setLog(Log log) {
123 this.log = log;
124 }
125
126 /***
127 * <p>Gets the current registry implementation.
128 * The registry is checked to see if it has an <code>XMLBeanInfo</code> for a class
129 * before introspecting.
130 * After standard introspection is complete, the instance will be passed to the registry.</p>
131 *
132 * <p>This allows finely grained control over the caching strategy.
133 * It also allows the standard introspection mechanism
134 * to be overridden on a per class basis.</p>
135 *
136 * @return the XMLBeanInfoRegistry currently used
137 */
138 public XMLBeanInfoRegistry getRegistry() {
139 return registry;
140 }
141
142 /***
143 * <p>Sets the <code>XMLBeanInfoRegistry</code> implementation.
144 * The registry is checked to see if it has an <code>XMLBeanInfo</code> for a class
145 * before introspecting.
146 * After standard introspection is complete, the instance will be passed to the registry.</p>
147 *
148 * <p>This allows finely grained control over the caching strategy.
149 * It also allows the standard introspection mechanism
150 * to be overridden on a per class basis.</p>
151 *
152 * @param registry the XMLBeanInfoRegistry to use
153 */
154 public void setRegistry(XMLBeanInfoRegistry registry) {
155 this.registry = registry;
156 }
157
158
159 /***
160 * Gets the <code>ClassNormalizer</code> strategy.
161 * This is used to determine the Class to be introspected
162 * (the normalized Class).
163 *
164 * @return the <code>ClassNormalizer</code> used to determine the Class to be introspected
165 * for a given Object.
166 * @since 0.5
167 */
168 public ClassNormalizer getClassNormalizer() {
169 return classNormalizer;
170 }
171
172 /***
173 * Sets the <code>ClassNormalizer</code> strategy.
174 * This is used to determine the Class to be introspected
175 * (the normalized Class).
176 *
177 * @param classNormalizer the <code>ClassNormalizer</code> to be used to determine
178 * the Class to be introspected for a given Object.
179 * @since 0.5
180 */
181 public void setClassNormalizer(ClassNormalizer classNormalizer) {
182 this.classNormalizer = classNormalizer;
183 }
184
185 /***
186 * Is <code>XMLBeanInfo</code> caching enabled?
187 *
188 * @deprecated 0.5 replaced by XMlBeanInfoRegistry
189 * @return true if caching is enabled
190 */
191 public boolean isCachingEnabled() {
192 return true;
193 }
194
195 /***
196 * Set whether <code>XMLBeanInfo</code> caching should be enabled.
197 *
198 * @deprecated 0.5 replaced by XMlBeanInfoRegistry
199 * @param cachingEnabled ignored
200 */
201 public void setCachingEnabled(boolean cachingEnabled) {
202
203 }
204
205 /***
206 * Flush existing cached <code>XMLBeanInfo</code>'s.
207 *
208 * @deprecated 0.5 use flushable registry instead
209 */
210 public void flushCache() {}
211
212 /*** Create a standard <code>XMLBeanInfo</code> by introspection
213 * The actual introspection depends only on the <code>BeanInfo</code>
214 * associated with the bean.
215 *
216 * @param bean introspect this bean
217 * @return XMLBeanInfo describing bean-xml mapping
218 * @throws IntrospectionException when the bean introspection fails
219 */
220 public XMLBeanInfo introspect(Object bean) throws IntrospectionException {
221 if (log.isDebugEnabled()) {
222 log.debug( "Introspecting..." );
223 log.debug(bean);
224 }
225
226 if ( bean instanceof DynaBean ) {
227
228 XMLBeanInfo xmlBeanInfo = findByXMLDescriptor( bean.getClass() );
229 if (xmlBeanInfo != null) {
230 return xmlBeanInfo;
231 }
232
233 return introspect( ((DynaBean) bean).getDynaClass() );
234
235 } else {
236
237 Class normalClass = getClassNormalizer().getNormalizedClass( bean );
238 return introspect( normalClass );
239 }
240 }
241
242 /***
243 * Creates XMLBeanInfo by reading the DynaProperties of a DynaBean.
244 * Customizing DynaBeans using betwixt is not supported.
245 *
246 * @param dynaClass the DynaBean to introspect
247 *
248 * @return XMLBeanInfo for the DynaClass
249 */
250 public XMLBeanInfo introspect(DynaClass dynaClass) {
251
252
253
254
255
256
257
258 XMLBeanInfo xmlInfo = createXMLBeanInfo( dynaClass );
259
260
261 DynaClassBeanType beanClass = new DynaClassBeanType( dynaClass );
262 populate( xmlInfo, beanClass );
263
264 return xmlInfo;
265 }
266
267 /*** Create a standard <code>XMLBeanInfo</code> by introspection.
268 * The actual introspection depends only on the <code>BeanInfo</code>
269 * associated with the bean.
270 *
271 * @param aClass introspect this class
272 * @return XMLBeanInfo describing bean-xml mapping
273 * @throws IntrospectionException when the bean introspection fails
274 */
275 public XMLBeanInfo introspect(Class aClass) throws IntrospectionException {
276
277 String[] searchPath = null;
278 if ( !useBeanInfoSearchPath ) {
279 searchPath = Introspector.getBeanInfoSearchPath();
280 Introspector.setBeanInfoSearchPath(new String[] { });
281 }
282
283 XMLBeanInfo xmlInfo = registry.get( aClass );
284
285 if ( xmlInfo == null ) {
286
287 if ( log.isDebugEnabled() ) {
288 log.debug( "Attempting to lookup an XML descriptor for class: " + aClass );
289 }
290
291 xmlInfo = findByXMLDescriptor( aClass );
292 if ( xmlInfo == null ) {
293 BeanInfo info = Introspector.getBeanInfo( aClass );
294 xmlInfo = introspect( info );
295 }
296
297 if ( xmlInfo != null ) {
298 registry.put( aClass, xmlInfo );
299 }
300 } else {
301 log.trace( "Used cached XMLBeanInfo." );
302 }
303
304 if ( log.isTraceEnabled() ) {
305 log.trace( xmlInfo );
306 }
307 if ( !useBeanInfoSearchPath ) {
308
309 Introspector.setBeanInfoSearchPath( searchPath );
310 }
311
312 return xmlInfo;
313 }
314
315 /*** Create a standard <code>XMLBeanInfo</code> by introspection.
316 * The actual introspection depends only on the <code>BeanInfo</code>
317 * associated with the bean.
318 *
319 * @param beanInfo the BeanInfo the xml-bean mapping is based on
320 * @return XMLBeanInfo describing bean-xml mapping
321 * @throws IntrospectionException when the bean introspection fails
322 */
323 public XMLBeanInfo introspect(BeanInfo beanInfo) throws IntrospectionException {
324 XMLBeanInfo xmlBeanInfo = createXMLBeanInfo( beanInfo );
325 populate( xmlBeanInfo, new JavaBeanType( beanInfo ) );
326 return xmlBeanInfo;
327 }
328
329 /***
330 * Populates the given <code>XMLBeanInfo</code> based on the given type of bean.
331 *
332 * @param xmlBeanInfo populate this, not null
333 * @param bean the type definition for the bean, not null
334 */
335 private void populate(XMLBeanInfo xmlBeanInfo, BeanType bean) {
336 String name = bean.getBeanName();
337
338 ElementDescriptor elementDescriptor = new ElementDescriptor();
339 elementDescriptor.setLocalName(
340 getElementNameMapper().mapTypeToElementName( name ) );
341 elementDescriptor.setPropertyType( bean.getElementType() );
342
343 if (log.isTraceEnabled()) {
344 log.trace("Populating:" + bean);
345 }
346
347
348 if ( bean.isPrimitiveType() ) {
349 log.trace("Bean is primitive");
350 elementDescriptor.setTextExpression( StringExpression.getInstance() );
351 elementDescriptor.setPrimitiveType(true);
352
353 } else if ( bean.isLoopType() ) {
354 log.trace("Bean is loop");
355 ElementDescriptor loopDescriptor = new ElementDescriptor();
356 loopDescriptor.setContextExpression(
357 new IteratorExpression( EmptyExpression.getInstance() )
358 );
359 if ( bean.isMapType() ) {
360 loopDescriptor.setQualifiedName( "entry" );
361 }
362 elementDescriptor.setElementDescriptors( new ElementDescriptor[] { loopDescriptor } );
363
364
365
366
367
368
369 } else {
370 log.trace("Bean is standard type");
371 List elements = new ArrayList();
372 List attributes = new ArrayList();
373 List contents = new ArrayList();
374
375 addProperties( bean.getProperties(), elements, attributes, contents );
376
377 int size = elements.size();
378 if ( size > 0 ) {
379 ElementDescriptor[] descriptors = new ElementDescriptor[size];
380 elements.toArray( descriptors );
381 elementDescriptor.setElementDescriptors( descriptors );
382 }
383 size = attributes.size();
384 if ( size > 0 ) {
385 AttributeDescriptor[] descriptors = new AttributeDescriptor[size];
386 attributes.toArray( descriptors );
387 elementDescriptor.setAttributeDescriptors( descriptors );
388 }
389 size = contents.size();
390 if ( size > 0 ) {
391 if ( size > 0 ) {
392 Descriptor[] descriptors = new Descriptor[size];
393 contents.toArray( descriptors );
394 elementDescriptor.setContentDescriptors( descriptors );
395 }
396 }
397 }
398
399 xmlBeanInfo.setElementDescriptor( elementDescriptor );
400
401
402 XMLIntrospectorHelper.defaultAddMethods( this, elementDescriptor, bean.getElementType() );
403
404 if (log.isTraceEnabled()) {
405 log.trace("Populated descriptor:");
406 log.trace(elementDescriptor);
407 }
408 }
409
410
411 /***
412 * Creates XMLBeanInfo for the given DynaClass.
413 *
414 * @param dynaClass the class describing a DynaBean
415 *
416 * @return XMLBeanInfo that describes the properties of the given
417 * DynaClass
418 */
419 protected XMLBeanInfo createXMLBeanInfo(DynaClass dynaClass) {
420
421 XMLBeanInfo beanInfo = new XMLBeanInfo(dynaClass.getClass());
422 return beanInfo;
423 }
424
425
426
427
428
429 /***
430 * Should attributes (or elements) be used for primitive types.
431 * @return true if primitive types will be mapped to attributes in the introspection
432 */
433 public boolean isAttributesForPrimitives() {
434 return attributesForPrimitives;
435 }
436
437 /***
438 * Set whether attributes (or elements) should be used for primitive types.
439 * @param attributesForPrimitives pass trus to map primitives to attributes,
440 * pass false to map primitives to elements
441 */
442 public void setAttributesForPrimitives(boolean attributesForPrimitives) {
443 this.attributesForPrimitives = attributesForPrimitives;
444 }
445
446 /***
447 * Should collections be wrapped in an extra element?
448 *
449 * @return whether we should we wrap collections in an extra element?
450 */
451 public boolean isWrapCollectionsInElement() {
452 return wrapCollectionsInElement;
453 }
454
455 /***
456 * Sets whether we should we wrap collections in an extra element.
457 *
458 * @param wrapCollectionsInElement pass true if collections should be wrapped in a
459 * parent element
460 */
461 public void setWrapCollectionsInElement(boolean wrapCollectionsInElement) {
462 this.wrapCollectionsInElement = wrapCollectionsInElement;
463 }
464
465 /***
466 * Get singular and plural matching strategy.
467 *
468 * @return the strategy used to detect matching singular and plural properties
469 */
470 public PluralStemmer getPluralStemmer() {
471 if ( pluralStemmer == null ) {
472 pluralStemmer = createPluralStemmer();
473 }
474 return pluralStemmer;
475 }
476
477 /***
478 * Sets the strategy used to detect matching singular and plural properties
479 *
480 * @param pluralStemmer the PluralStemmer used to match singular and plural
481 */
482 public void setPluralStemmer(PluralStemmer pluralStemmer) {
483 this.pluralStemmer = pluralStemmer;
484 }
485
486 /***
487 * Gets the name mapper strategy.
488 *
489 * @return the strategy used to convert bean type names into element names
490 * @deprecated 0.5 getNameMapper is split up in
491 * {@link #getElementNameMapper()} and {@link #getAttributeNameMapper()}
492 */
493 public NameMapper getNameMapper() {
494 return getElementNameMapper();
495 }
496
497 /***
498 * Sets the strategy used to convert bean type names into element names
499 * @param nameMapper the NameMapper strategy to be used
500 * @deprecated 0.5 setNameMapper is split up in
501 * {@link #setElementNameMapper(NameMapper)} and {@link #setAttributeNameMapper(NameMapper)}
502 */
503 public void setNameMapper(NameMapper nameMapper) {
504 setElementNameMapper(nameMapper);
505 }
506
507
508 /***
509 * Gets the name mapping strategy used to convert bean names into elements.
510 *
511 * @return the strategy used to convert bean type names into element
512 * names. If no element mapper is currently defined then a default one is created.
513 */
514 public NameMapper getElementNameMapper() {
515 if ( elementNameMapper == null ) {
516 elementNameMapper = createNameMapper();
517 }
518 return elementNameMapper;
519 }
520
521 /***
522 * Sets the strategy used to convert bean type names into element names
523 * @param nameMapper the NameMapper to use for the conversion
524 */
525 public void setElementNameMapper(NameMapper nameMapper) {
526 this.elementNameMapper = nameMapper;
527 }
528
529
530 /***
531 * Gets the name mapping strategy used to convert bean names into attributes.
532 *
533 * @return the strategy used to convert bean type names into attribute
534 * names. If no attributeNamemapper is known, it will default to the ElementNameMapper
535 */
536 public NameMapper getAttributeNameMapper() {
537 if (attributeNameMapper == null) {
538 attributeNameMapper = createNameMapper();
539 }
540 return attributeNameMapper;
541 }
542
543
544 /***
545 * Sets the strategy used to convert bean type names into attribute names
546 * @param nameMapper the NameMapper to use for the convertion
547 */
548 public void setAttributeNameMapper(NameMapper nameMapper) {
549 this.attributeNameMapper = nameMapper;
550 }
551
552 /***
553 * Create a XML descriptor from a bean one.
554 * Go through and work out whether it's a loop property, a primitive or a standard.
555 * The class property is ignored.
556 *
557 * @param propertyDescriptor create a <code>NodeDescriptor</code> for this property
558 * @param useAttributesForPrimitives write primitives as attributes (rather than elements)
559 * @return a correctly configured <code>NodeDescriptor</code> for the property
560 * @throws IntrospectionException when bean introspection fails
561 * @deprecated 0.5 use {@link #createXMLDescriptor}.
562 */
563 public Descriptor createDescriptor(
564 PropertyDescriptor propertyDescriptor,
565 boolean useAttributesForPrimitives
566 ) throws IntrospectionException {
567 return createXMLDescriptor( new BeanProperty( propertyDescriptor ) );
568 }
569
570 /***
571 * Create a XML descriptor from a bean one.
572 * Go through and work out whether it's a loop property, a primitive or a standard.
573 * The class property is ignored.
574 *
575 * @param beanProperty the BeanProperty specifying the property
576 * @return a correctly configured <code>NodeDescriptor</code> for the property
577 * @since 0.5
578 */
579 public Descriptor createXMLDescriptor( BeanProperty beanProperty ) {
580 String name = beanProperty.getPropertyName();
581 Class type = beanProperty.getPropertyType();
582
583 if (log.isTraceEnabled()) {
584 log.trace("Creating descriptor for property: name="
585 + name + " type=" + type);
586 }
587
588 Descriptor descriptor = null;
589 Expression propertyExpression = beanProperty.getPropertyExpression();
590 Updater propertyUpdater = beanProperty.getPropertyUpdater();
591
592 if ( propertyExpression == null ) {
593 if (log.isTraceEnabled()) {
594 log.trace( "No read method for property: name="
595 + name + " type=" + type);
596 }
597 return null;
598 }
599
600 if ( log.isTraceEnabled() ) {
601 log.trace( "Property expression=" + propertyExpression );
602 }
603
604
605
606
607 if ( Class.class.equals( type ) && "class".equals( name ) ) {
608 log.trace( "Ignoring class property" );
609 return null;
610
611 }
612
613 if ( isPrimitiveType( type ) ) {
614 if (log.isTraceEnabled()) {
615 log.trace( "Primitive type: " + name);
616 }
617 if ( isAttributesForPrimitives() ) {
618 if (log.isTraceEnabled()) {
619 log.trace( "Adding property as attribute: " + name );
620 }
621 descriptor = new AttributeDescriptor();
622 } else {
623 if (log.isTraceEnabled()) {
624 log.trace( "Adding property as element: " + name );
625 }
626 descriptor = new ElementDescriptor(true);
627 }
628 descriptor.setTextExpression( propertyExpression );
629 if ( propertyUpdater != null ) {
630 descriptor.setUpdater( propertyUpdater );
631 }
632
633 } else if ( isLoopType( type ) ) {
634 if (log.isTraceEnabled()) {
635 log.trace("Loop type: " + name);
636 log.trace("Wrap in collections? " + isWrapCollectionsInElement());
637 }
638 ElementDescriptor loopDescriptor = new ElementDescriptor();
639 loopDescriptor.setContextExpression(
640 new IteratorExpression( propertyExpression )
641 );
642 loopDescriptor.setWrapCollectionsInElement( isWrapCollectionsInElement() );
643
644
645 if ( Map.class.isAssignableFrom( type ) ) {
646 loopDescriptor.setQualifiedName( "entry" );
647
648 loopDescriptor.addElementDescriptor( new ElementDescriptor( "key" ) );
649 loopDescriptor.addElementDescriptor( new ElementDescriptor( "value" ) );
650 }
651
652 ElementDescriptor elementDescriptor = new ElementDescriptor();
653 elementDescriptor.setWrapCollectionsInElement( isWrapCollectionsInElement() );
654 elementDescriptor.setElementDescriptors( new ElementDescriptor[] { loopDescriptor } );
655
656 descriptor = elementDescriptor;
657
658 } else {
659 if (log.isTraceEnabled()) {
660 log.trace( "Standard property: " + name);
661 }
662 ElementDescriptor elementDescriptor = new ElementDescriptor();
663 elementDescriptor.setContextExpression( propertyExpression );
664 if ( propertyUpdater != null ) {
665 elementDescriptor.setUpdater( propertyUpdater );
666 }
667
668 descriptor = elementDescriptor;
669 }
670
671 if (descriptor instanceof NodeDescriptor) {
672 NodeDescriptor nodeDescriptor = (NodeDescriptor) descriptor;
673 if (descriptor instanceof AttributeDescriptor) {
674
675 nodeDescriptor.setLocalName(
676 getAttributeNameMapper().mapTypeToElementName( name ) );
677
678 } else {
679 nodeDescriptor.setLocalName(
680 getElementNameMapper().mapTypeToElementName( name ) );
681 }
682 }
683
684 descriptor.setPropertyName( name );
685 descriptor.setPropertyType( type );
686
687
688
689
690
691 if (log.isTraceEnabled()) {
692 log.trace( "Created descriptor:" );
693 log.trace( descriptor );
694 }
695 return descriptor;
696 }
697
698
699
700
701
702
703
704 /***
705 * A Factory method to lazily create a new strategy
706 * to detect matching singular and plural properties.
707 *
708 * @return new defualt PluralStemmer implementation
709 */
710 protected PluralStemmer createPluralStemmer() {
711 return new DefaultPluralStemmer();
712 }
713
714 /***
715 * A Factory method to lazily create a strategy
716 * used to convert bean type names into element names.
717 *
718 * @return new default NameMapper implementation
719 */
720 protected NameMapper createNameMapper() {
721 return new DefaultNameMapper();
722 }
723
724 /***
725 * Attempt to lookup the XML descriptor for the given class using the
726 * classname + ".betwixt" using the same ClassLoader used to load the class
727 * or return null if it could not be loaded
728 *
729 * @param aClass digester .betwixt file for this class
730 * @return XMLBeanInfo digested from the .betwixt file if one can be found.
731 * Otherwise null.
732 */
733 protected synchronized XMLBeanInfo findByXMLDescriptor( Class aClass ) {
734
735 String name = aClass.getName();
736 int idx = name.lastIndexOf( '.' );
737 if ( idx >= 0 ) {
738 name = name.substring( idx + 1 );
739 }
740 name += ".betwixt";
741
742 URL url = aClass.getResource( name );
743 if ( url != null ) {
744 try {
745 String urlText = url.toString();
746 if ( log.isDebugEnabled( )) {
747 log.debug( "Parsing Betwixt XML descriptor: " + urlText );
748 }
749
750
751 if ( digester == null ) {
752 digester = new XMLBeanInfoDigester();
753 digester.setXMLIntrospector( this );
754 }
755 digester.setBeanClass( aClass );
756 return (XMLBeanInfo) digester.parse( urlText );
757 } catch (Exception e) {
758 log.warn( "Caught exception trying to parse: " + name, e );
759 }
760 }
761
762 if ( log.isTraceEnabled() ) {
763 log.trace( "Could not find betwixt file " + name );
764 }
765 return null;
766 }
767
768 /***
769 * Loop through properties and process each one
770 *
771 * @param beanInfo the BeanInfo whose properties will be processed
772 * @param elements ElementDescriptor list to which elements will be added
773 * @param attributes AttributeDescriptor list to which attributes will be added
774 * @param contents Descriptor list to which mixed content will be added
775 * @throws IntrospectionException if the bean introspection fails
776 * @deprecated 0.5 use {@link #addProperties(BeanProperty[], List, List,List)}
777 */
778 protected void addProperties(
779 BeanInfo beanInfo,
780 List elements,
781 List attributes,
782 List contents)
783 throws
784 IntrospectionException {
785 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
786 if ( descriptors != null ) {
787 for ( int i = 0, size = descriptors.length; i < size; i++ ) {
788 addProperty(beanInfo, descriptors[i], elements, attributes, contents);
789 }
790 }
791 if (log.isTraceEnabled()) {
792 log.trace(elements);
793 log.trace(attributes);
794 log.trace(contents);
795 }
796 }
797 /***
798 * Loop through properties and process each one
799 *
800 * @param beanProperties the properties to be processed
801 * @param elements ElementDescriptor list to which elements will be added
802 * @param attributes AttributeDescriptor list to which attributes will be added
803 * @param contents Descriptor list to which mixed content will be added
804 * @since 0.5
805 */
806 protected void addProperties(
807 BeanProperty[] beanProperties,
808 List elements,
809 List attributes,
810 List contents) {
811 if ( beanProperties != null ) {
812 if (log.isTraceEnabled()) {
813 log.trace(beanProperties.length + " properties to be added");
814 }
815 for ( int i = 0, size = beanProperties.length; i < size; i++ ) {
816 addProperty(beanProperties[i], elements, attributes, contents);
817 }
818 }
819 if (log.isTraceEnabled()) {
820 log.trace("After properties have been added (elements, attributes, contents):");
821 log.trace(elements);
822 log.trace(attributes);
823 log.trace(contents);
824 }
825 }
826
827
828 /***
829 * Process a property.
830 * Go through and work out whether it's a loop property, a primitive or a standard.
831 * The class property is ignored.
832 *
833 * @param beanInfo the BeanInfo whose property is being processed
834 * @param propertyDescriptor the PropertyDescriptor to process
835 * @param elements ElementDescriptor list to which elements will be added
836 * @param attributes AttributeDescriptor list to which attributes will be added
837 * @param contents Descriptor list to which mixed content will be added
838 * @throws IntrospectionException if the bean introspection fails
839 * @deprecated 0.5 BeanInfo is no longer required.
840 * Use {@link #addProperty(PropertyDescriptor, List, List, List)} instead.
841 */
842 protected void addProperty(
843 BeanInfo beanInfo,
844 PropertyDescriptor propertyDescriptor,
845 List elements,
846 List attributes,
847 List contents)
848 throws
849 IntrospectionException {
850 addProperty( propertyDescriptor, elements, attributes, contents);
851 }
852
853 /***
854 * Process a property.
855 * Go through and work out whether it's a loop property, a primitive or a standard.
856 * The class property is ignored.
857 *
858 * @param propertyDescriptor the PropertyDescriptor to process
859 * @param elements ElementDescriptor list to which elements will be added
860 * @param attributes AttributeDescriptor list to which attributes will be added
861 * @param contents Descriptor list to which mixed content will be added
862 * @throws IntrospectionException if the bean introspection fails
863 * @deprecated 0.5 use {@link #addProperty(BeanProperty, List, List, List)} instead
864 */
865 protected void addProperty(
866 PropertyDescriptor propertyDescriptor,
867 List elements,
868 List attributes,
869 List contents)
870 throws
871 IntrospectionException {
872 addProperty(new BeanProperty( propertyDescriptor ), elements, attributes, contents);
873 }
874
875 /***
876 * Process a property.
877 * Go through and work out whether it's a loop property, a primitive or a standard.
878 * The class property is ignored.
879 *
880 * @param beanProperty the bean property to process
881 * @param elements ElementDescriptor list to which elements will be added
882 * @param attributes AttributeDescriptor list to which attributes will be added
883 * @param contents Descriptor list to which mixed content will be added
884 * @since 0.5
885 */
886 protected void addProperty(
887 BeanProperty beanProperty,
888 List elements,
889 List attributes,
890 List contents) {
891 Descriptor nodeDescriptor = createXMLDescriptor(beanProperty);
892 if (nodeDescriptor == null) {
893 return;
894 }
895 if (nodeDescriptor instanceof ElementDescriptor) {
896 elements.add(nodeDescriptor);
897 } else if (nodeDescriptor instanceof AttributeDescriptor) {
898 attributes.add(nodeDescriptor);
899 } else {
900 contents.add(nodeDescriptor);
901 }
902 }
903
904 /***
905 * Loop through properties and process each one
906 *
907 * @param beanInfo the BeanInfo whose properties will be processed
908 * @param elements ElementDescriptor list to which elements will be added
909 * @param attributes AttributeDescriptor list to which attributes will be added
910 * @throws IntrospectionException if the bean introspection fails
911 * @deprecated 0.5 this method does not support mixed content.
912 * Use {@link #addProperties(BeanInfo, List, List, List)} instead.
913 */
914 protected void addProperties(
915 BeanInfo beanInfo,
916 List elements,
917 List attributes)
918 throws
919 IntrospectionException {
920 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
921 if ( descriptors != null ) {
922 for ( int i = 0, size = descriptors.length; i < size; i++ ) {
923 addProperty(beanInfo, descriptors[i], elements, attributes);
924 }
925 }
926 if (log.isTraceEnabled()) {
927 log.trace(elements);
928 log.trace(attributes);
929 }
930 }
931
932 /***
933 * Process a property.
934 * Go through and work out whether it's a loop property, a primitive or a standard.
935 * The class property is ignored.
936 *
937 * @param beanInfo the BeanInfo whose property is being processed
938 * @param propertyDescriptor the PropertyDescriptor to process
939 * @param elements ElementDescriptor list to which elements will be added
940 * @param attributes AttributeDescriptor list to which attributes will be added
941 * @throws IntrospectionException if the bean introspection fails
942 * @deprecated 0.5 this method does not support mixed content.
943 * Use {@link #addProperty(BeanInfo, PropertyDescriptor, List, List, List)} instead.
944 */
945 protected void addProperty(
946 BeanInfo beanInfo,
947 PropertyDescriptor propertyDescriptor,
948 List elements,
949 List attributes)
950 throws
951 IntrospectionException {
952 NodeDescriptor nodeDescriptor = XMLIntrospectorHelper
953 .createDescriptor(propertyDescriptor,
954 isAttributesForPrimitives(),
955 this);
956 if (nodeDescriptor == null) {
957 return;
958 }
959 if (nodeDescriptor instanceof ElementDescriptor) {
960 elements.add(nodeDescriptor);
961 } else {
962 attributes.add(nodeDescriptor);
963 }
964 }
965
966
967 /***
968 * Factory method to create XMLBeanInfo instances
969 *
970 * @param beanInfo the BeanInfo from which the XMLBeanInfo will be created
971 * @return XMLBeanInfo describing the bean-xml mapping
972 */
973 protected XMLBeanInfo createXMLBeanInfo( BeanInfo beanInfo ) {
974 XMLBeanInfo xmlBeanInfo = new XMLBeanInfo( beanInfo.getBeanDescriptor().getBeanClass() );
975 return xmlBeanInfo;
976 }
977
978 /***
979 * Is this class a loop?
980 *
981 * @param type the Class to test
982 * @return true if the type is a loop type
983 */
984 public boolean isLoopType(Class type) {
985 return XMLIntrospectorHelper.isLoopType(type);
986 }
987
988
989 /***
990 * Is this class a primitive?
991 * @param type the Class to test
992 * @return true for primitive types
993 */
994 public boolean isPrimitiveType(Class type) {
995 return XMLIntrospectorHelper.isPrimitiveType(type);
996 }
997 /***
998 * Should the original <code>java.reflect.Introspector</code> bean info search path be used?
999 * By default it will be false.
1000 *
1001 * @return boolean if the beanInfoSearchPath should be used.
1002 */
1003 public boolean useBeanInfoSearchPath() {
1004 return useBeanInfoSearchPath;
1005 }
1006
1007 /***
1008 * Specifies if you want to use the beanInfoSearchPath
1009 * @see java.beans.Introspector for more details
1010 * @param useBeanInfoSearchPath
1011 */
1012 public void setUseBeanInfoSearchPath(boolean useBeanInfoSearchPath) {
1013 this.useBeanInfoSearchPath = useBeanInfoSearchPath;
1014 }
1015
1016 /*** Some type of pseudo-bean */
1017 private abstract class BeanType {
1018 /***
1019 * Gets the name for this bean type
1020 * @return the bean type name, not null
1021 */
1022 public abstract String getBeanName();
1023
1024 /***
1025 * Gets the type to be used by the associated element
1026 * @return a Class that is the type not null
1027 */
1028 public abstract Class getElementType();
1029
1030 /***
1031 * Is this type a primitive?
1032 * @return true if this type should be treated by betwixt as a primitive
1033 */
1034 public abstract boolean isPrimitiveType();
1035
1036 /***
1037 * is this type a map?
1038 * @return true this should be treated as a map.
1039 */
1040 public abstract boolean isMapType();
1041
1042 /***
1043 * Is this type a loop?
1044 * @return true if this should be treated as a loop
1045 */
1046 public abstract boolean isLoopType();
1047
1048 /***
1049 * Gets the properties associated with this bean.
1050 * @return the BeanProperty's, not null
1051 */
1052 public abstract BeanProperty[] getProperties();
1053
1054 /***
1055 * Create string representation
1056 * @return something useful for logging
1057 */
1058 public String toString() {
1059 return "Bean[name=" + getBeanName() + ", type=" + getElementType();
1060 }
1061 }
1062
1063 /*** Supports standard Java Beans */
1064 private class JavaBeanType extends BeanType {
1065 /*** Introspected bean */
1066 private BeanInfo beanInfo;
1067 /*** Bean class */
1068 private Class beanClass;
1069 /*** Bean name */
1070 private String name;
1071 /*** Bean properties */
1072 private BeanProperty[] properties;
1073
1074 /***
1075 * Constructs a BeanType for a standard Java Bean
1076 * @param beanInfo the BeanInfo describing the standard Java Bean, not null
1077 */
1078 public JavaBeanType(BeanInfo beanInfo) {
1079 this.beanInfo = beanInfo;
1080 BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
1081 beanClass = beanDescriptor.getBeanClass();
1082 name = beanDescriptor.getName();
1083
1084 if (beanClass.isArray()) {
1085
1086 name = "Array";
1087 }
1088
1089 }
1090
1091 /*** @see BeanType #getElementType */
1092 public Class getElementType() {
1093 return beanClass;
1094 }
1095
1096 /*** @see BeanType#getBeanName */
1097 public String getBeanName() {
1098 return name;
1099 }
1100
1101 /*** @see BeanType#isPrimitiveType */
1102 public boolean isPrimitiveType() {
1103 return XMLIntrospectorHelper.isPrimitiveType( beanClass );
1104 }
1105
1106 /*** @see BeanType#isLoopType */
1107 public boolean isLoopType() {
1108 return XMLIntrospectorHelper.isLoopType( beanClass );
1109 }
1110
1111 /*** @see BeanType#isMapType */
1112 public boolean isMapType() {
1113 return Map.class.isAssignableFrom( beanClass );
1114 }
1115
1116 /*** @see BeanType#getProperties */
1117 public BeanProperty[] getProperties() {
1118
1119 if ( properties == null ) {
1120 ArrayList propertyDescriptors = new ArrayList();
1121
1122 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
1123 if ( descriptors != null ) {
1124 for (int i=0, size=descriptors.length; i<size; i++) {
1125 propertyDescriptors.add( descriptors[i] );
1126 }
1127 }
1128
1129
1130 BeanInfo[] additionals = beanInfo.getAdditionalBeanInfo();
1131 if ( additionals != null ) {
1132 for ( int i=0, outerSize=additionals.length; i<outerSize; i++ ) {
1133 BeanInfo additionalInfo = additionals[i];
1134 descriptors = beanInfo.getPropertyDescriptors();
1135 if ( descriptors != null ) {
1136 for (int j=0, innerSize=descriptors.length; j<innerSize; j++) {
1137 propertyDescriptors.add( descriptors[j] );
1138 }
1139 }
1140 }
1141 }
1142
1143 properties = new BeanProperty[ propertyDescriptors.size() ];
1144 int count = 0;
1145 for ( Iterator it = propertyDescriptors.iterator(); it.hasNext(); count++) {
1146 PropertyDescriptor propertyDescriptor = (PropertyDescriptor) it.next();
1147 properties[count] = new BeanProperty( propertyDescriptor );
1148 }
1149 }
1150 return properties;
1151 }
1152 }
1153
1154 /*** Implementation for DynaClasses */
1155 private class DynaClassBeanType extends BeanType {
1156 /*** BeanType for this DynaClass */
1157 private DynaClass dynaClass;
1158 /*** Properties extracted in constuctor */
1159 private BeanProperty[] properties;
1160
1161 /***
1162 * Constructs a BeanType for a DynaClass
1163 * @param dynaClass not null
1164 */
1165 public DynaClassBeanType(DynaClass dynaClass) {
1166 this.dynaClass = dynaClass;
1167 DynaProperty[] dynaProperties = dynaClass.getDynaProperties();
1168 properties = new BeanProperty[dynaProperties.length];
1169 for (int i=0, size=dynaProperties.length; i<size; i++) {
1170 properties[i] = new BeanProperty(dynaProperties[i]);
1171 }
1172 }
1173
1174 /*** @see BeanType#getBeanName */
1175 public String getBeanName() {
1176 return dynaClass.getName();
1177 }
1178 /*** @see BeanType#getElementType */
1179 public Class getElementType() {
1180 return DynaClass.class;
1181 }
1182 /*** @see BeanType#isPrimitiveType */
1183 public boolean isPrimitiveType() {
1184 return false;
1185 }
1186 /*** @see BeanType#isMapType */
1187 public boolean isMapType() {
1188 return false;
1189 }
1190 /*** @see BeanType#isLoopType */
1191 public boolean isLoopType() {
1192 return false;
1193 }
1194 /*** @see BeanType#getProperties */
1195 public BeanProperty[] getProperties() {
1196 return properties;
1197 }
1198 }
1199 }