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.io.IOException;
25 import java.lang.reflect.Method;
26 import java.net.URL;
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Set;
33
34 import org.apache.commons.beanutils.DynaBean;
35 import org.apache.commons.beanutils.DynaClass;
36 import org.apache.commons.beanutils.DynaProperty;
37 import org.apache.commons.betwixt.digester.MultiMappingBeanInfoDigester;
38 import org.apache.commons.betwixt.digester.XMLBeanInfoDigester;
39 import org.apache.commons.betwixt.digester.XMLIntrospectorHelper;
40 import org.apache.commons.betwixt.expression.EmptyExpression;
41 import org.apache.commons.betwixt.expression.IteratorExpression;
42 import org.apache.commons.betwixt.expression.MapEntryAdder;
43 import org.apache.commons.betwixt.expression.MethodUpdater;
44 import org.apache.commons.betwixt.expression.StringExpression;
45 import org.apache.commons.betwixt.registry.DefaultXMLBeanInfoRegistry;
46 import org.apache.commons.betwixt.registry.PolymorphicReferenceResolver;
47 import org.apache.commons.betwixt.registry.XMLBeanInfoRegistry;
48 import org.apache.commons.betwixt.strategy.ClassNormalizer;
49 import org.apache.commons.betwixt.strategy.DefaultNameMapper;
50 import org.apache.commons.betwixt.strategy.DefaultPluralStemmer;
51 import org.apache.commons.betwixt.strategy.NameMapper;
52 import org.apache.commons.betwixt.strategy.PluralStemmer;
53 import org.apache.commons.betwixt.strategy.TypeBindingStrategy;
54 import org.apache.commons.logging.Log;
55 import org.apache.commons.logging.LogFactory;
56 import org.xml.sax.InputSource;
57 import org.xml.sax.SAXException;
58
59 /***
60 * <p><code>XMLIntrospector</code> an introspector of beans to create a
61 * XMLBeanInfo instance.</p>
62 *
63 * <p>By default, <code>XMLBeanInfo</code> caching is switched on.
64 * This means that the first time that a request is made for a <code>XMLBeanInfo</code>
65 * for a particular class, the <code>XMLBeanInfo</code> is cached.
66 * Later requests for the same class will return the cached value.</p>
67 *
68 * <p>Note :</p>
69 * <p>This class makes use of the <code>java.bean.Introspector</code>
70 * class, which contains a BeanInfoSearchPath. To make sure betwixt can
71 * do his work correctly, this searchpath is completely ignored during
72 * processing. The original values will be restored after processing finished
73 * </p>
74 *
75 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
76 * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
77 */
78 public class XMLIntrospector {
79 /***
80 * Log used for logging (Doh!)
81 * @deprecated 0.6 use the {@link #getLog()} property instead
82 */
83 protected Log log = LogFactory.getLog( XMLIntrospector.class );
84
85 /*** Maps classes to <code>XMLBeanInfo</code>'s */
86 private XMLBeanInfoRegistry registry;
87
88 /*** Digester used to parse the XML descriptor files */
89 private XMLBeanInfoDigester digester;
90
91 /*** Digester used to parse the multi-mapping XML descriptor files */
92 private MultiMappingBeanInfoDigester multiMappingdigester;
93
94 /*** Configuration to be used for introspection*/
95 private IntrospectionConfiguration configuration;
96
97 /***
98 * Resolves polymorphic references.
99 * Though this is used only at bind time,
100 * it is typically tightly couple to the xml registry.
101 * It is therefore convenient to keep both references together.
102 */
103 private PolymorphicReferenceResolver polymorphicReferenceResolver;
104
105 /*** Base constructor */
106 public XMLIntrospector() {
107 this(new IntrospectionConfiguration());
108 }
109
110 /***
111 * Construct allows a custom configuration to be set on construction.
112 * This allows <code>IntrospectionConfiguration</code> subclasses
113 * to be easily used.
114 * @param configuration IntrospectionConfiguration, not null
115 */
116 public XMLIntrospector(IntrospectionConfiguration configuration) {
117 setConfiguration(configuration);
118 DefaultXMLBeanInfoRegistry defaultRegistry
119 = new DefaultXMLBeanInfoRegistry();
120 setRegistry(defaultRegistry);
121 setPolymorphicReferenceResolver(defaultRegistry);
122 }
123
124
125
126
127
128 /***
129 * <p>Gets the current logging implementation. </p>
130 * @return the Log implementation which this class logs to
131 */
132 public Log getLog() {
133 return getConfiguration().getIntrospectionLog();
134 }
135
136 /***
137 * <p>Sets the current logging implementation.</p>
138 * @param log the Log implementation to use for logging
139 */
140 public void setLog(Log log) {
141 getConfiguration().setIntrospectionLog(log);
142 }
143
144 /***
145 * <p>Gets the current registry implementation.
146 * The registry is checked to see if it has an <code>XMLBeanInfo</code> for a class
147 * before introspecting.
148 * After standard introspection is complete, the instance will be passed to the registry.</p>
149 *
150 * <p>This allows finely grained control over the caching strategy.
151 * It also allows the standard introspection mechanism
152 * to be overridden on a per class basis.</p>
153 *
154 * @return the XMLBeanInfoRegistry currently used
155 */
156 public XMLBeanInfoRegistry getRegistry() {
157 return registry;
158 }
159
160 /***
161 * <p>Sets the <code>XMLBeanInfoRegistry</code> implementation.
162 * The registry is checked to see if it has an <code>XMLBeanInfo</code> for a class
163 * before introspecting.
164 * After standard introspection is complete, the instance will be passed to the registry.</p>
165 *
166 * <p>This allows finely grained control over the caching strategy.
167 * It also allows the standard introspection mechanism
168 * to be overridden on a per class basis.</p>
169 *
170 * <p><strong>Note</strong> when using polymophic mapping with a custom
171 * registry, a call to
172 * {@link #setPolymorphicReferenceResolver(PolymorphicReferenceResolver)}
173 * may be necessary.
174 * </p>
175 * @param registry the XMLBeanInfoRegistry to use
176 */
177 public void setRegistry(XMLBeanInfoRegistry registry) {
178 this.registry = registry;
179 }
180
181 /***
182 * Gets the configuration to be used for introspection.
183 * The various introspection-time strategies
184 * and configuration variables have been consolidated as properties
185 * of this bean.
186 * This allows the configuration to be more easily shared.
187 * @return IntrospectionConfiguration, not null
188 */
189 public IntrospectionConfiguration getConfiguration() {
190 return configuration;
191 }
192
193 /***
194 * Sets the configuration to be used for introspection.
195 * The various introspection-time strategies
196 * and configuration variables have been consolidated as properties
197 * of this bean.
198 * This allows the configuration to be more easily shared.
199 * @param configuration IntrospectionConfiguration, not null
200 */
201 public void setConfiguration(IntrospectionConfiguration configuration) {
202 this.configuration = configuration;
203 }
204
205
206 /***
207 * Gets the <code>ClassNormalizer</code> strategy.
208 * This is used to determine the Class to be introspected
209 * (the normalized Class).
210 *
211 * @return the <code>ClassNormalizer</code> used to determine the Class to be introspected
212 * for a given Object.
213 * @deprecated 0.6 use getConfiguration().getClassNormalizer
214 * @since 0.5
215 */
216 public ClassNormalizer getClassNormalizer() {
217 return getConfiguration().getClassNormalizer();
218 }
219
220 /***
221 * Sets the <code>ClassNormalizer</code> strategy.
222 * This is used to determine the Class to be introspected
223 * (the normalized Class).
224 *
225 * @param classNormalizer the <code>ClassNormalizer</code> to be used to determine
226 * the Class to be introspected for a given Object.
227 * @deprecated 0.6 use getConfiguration().setClassNormalizer
228 * @since 0.5
229 *
230 */
231 public void setClassNormalizer(ClassNormalizer classNormalizer) {
232 getConfiguration().setClassNormalizer(classNormalizer);
233 }
234
235
236
237 /***
238 * <p>Gets the resolver for polymorphic references.</p>
239 * <p>
240 * Though this is used only at bind time,
241 * it is typically tightly couple to the xml registry.
242 * It is therefore convenient to keep both references together.
243 * </p>
244 * <p><strong>Note:</strong> though the implementation is
245 * set initially to the default registry,
246 * this reference is not updated when {@link #setRegistry(XMLBeanInfoRegistry)}
247 * is called. Therefore, a call to {@link #setPolymorphicReferenceResolver(PolymorphicReferenceResolver)}
248 * with the instance may be necessary.
249 * </p>
250 * @since 0.7
251 * @return <code>PolymorphicReferenceResolver</code>, not null
252 */
253 public PolymorphicReferenceResolver getPolymorphicReferenceResolver() {
254 return polymorphicReferenceResolver;
255 }
256
257 /***
258 * <p>Sets the resolver for polymorphic references.</p>
259 * <p>
260 * Though this is used only at bind time,
261 * it is typically tightly couple to the xml registry.
262 * It is therefore convenient to keep both references together.
263 * </p>
264 * <p><strong>Note:</strong> though the implementation is
265 * set initially to the default registry,
266 * this reference is not updated when {@link #setRegistry(XMLBeanInfoRegistry)}
267 * is called. Therefore, a call to {@link #setPolymorphicReferenceResolver(PolymorphicReferenceResolver)}
268 * with the instance may be necessary.
269 * </p>
270 * @since 0.7
271 * @param polymorphicReferenceResolver The polymorphicReferenceResolver to set.
272 */
273 public void setPolymorphicReferenceResolver(
274 PolymorphicReferenceResolver polymorphicReferenceResolver) {
275 this.polymorphicReferenceResolver = polymorphicReferenceResolver;
276 }
277
278 /***
279 * Is <code>XMLBeanInfo</code> caching enabled?
280 *
281 * @deprecated 0.5 replaced by XMlBeanInfoRegistry
282 * @return true if caching is enabled
283 */
284 public boolean isCachingEnabled() {
285 return true;
286 }
287
288 /***
289 * Set whether <code>XMLBeanInfo</code> caching should be enabled.
290 *
291 * @deprecated 0.5 replaced by XMlBeanInfoRegistry
292 * @param cachingEnabled ignored
293 */
294 public void setCachingEnabled(boolean cachingEnabled) {
295
296 }
297
298
299 /***
300 * Should attributes (or elements) be used for primitive types.
301 * @return true if primitive types will be mapped to attributes in the introspection
302 * @deprecated 0.6 use getConfiguration().isAttributesForPrimitives
303 */
304 public boolean isAttributesForPrimitives() {
305 return getConfiguration().isAttributesForPrimitives();
306 }
307
308 /***
309 * Set whether attributes (or elements) should be used for primitive types.
310 * @param attributesForPrimitives pass trus to map primitives to attributes,
311 * pass false to map primitives to elements
312 * @deprecated 0.6 use getConfiguration().setAttributesForPrimitives
313 */
314 public void setAttributesForPrimitives(boolean attributesForPrimitives) {
315 getConfiguration().setAttributesForPrimitives(attributesForPrimitives);
316 }
317
318 /***
319 * Should collections be wrapped in an extra element?
320 *
321 * @return whether we should we wrap collections in an extra element?
322 * @deprecated 0.6 use getConfiguration().isWrapCollectionsInElement
323 */
324 public boolean isWrapCollectionsInElement() {
325 return getConfiguration().isWrapCollectionsInElement();
326 }
327
328 /***
329 * Sets whether we should we wrap collections in an extra element.
330 *
331 * @param wrapCollectionsInElement pass true if collections should be wrapped in a
332 * parent element
333 * @deprecated 0.6 use getConfiguration().setWrapCollectionsInElement
334 */
335 public void setWrapCollectionsInElement(boolean wrapCollectionsInElement) {
336 getConfiguration().setWrapCollectionsInElement(wrapCollectionsInElement);
337 }
338
339 /***
340 * Get singular and plural matching strategy.
341 *
342 * @return the strategy used to detect matching singular and plural properties
343 * @deprecated 0.6 use getConfiguration().getPluralStemmer
344 */
345 public PluralStemmer getPluralStemmer() {
346 return getConfiguration().getPluralStemmer();
347 }
348
349 /***
350 * Sets the strategy used to detect matching singular and plural properties
351 *
352 * @param pluralStemmer the PluralStemmer used to match singular and plural
353 * @deprecated 0.6 use getConfiguration().setPluralStemmer
354 */
355 public void setPluralStemmer(PluralStemmer pluralStemmer) {
356 getConfiguration().setPluralStemmer(pluralStemmer);
357 }
358
359 /***
360 * Gets the name mapper strategy.
361 *
362 * @return the strategy used to convert bean type names into element names
363 * @deprecated 0.5 getNameMapper is split up in
364 * {@link #getElementNameMapper()} and {@link #getAttributeNameMapper()}
365 */
366 public NameMapper getNameMapper() {
367 return getElementNameMapper();
368 }
369
370 /***
371 * Sets the strategy used to convert bean type names into element names
372 * @param nameMapper the NameMapper strategy to be used
373 * @deprecated 0.5 setNameMapper is split up in
374 * {@link #setElementNameMapper(NameMapper)} and {@link #setAttributeNameMapper(NameMapper)}
375 */
376 public void setNameMapper(NameMapper nameMapper) {
377 setElementNameMapper(nameMapper);
378 }
379
380
381 /***
382 * Gets the name mapping strategy used to convert bean names into elements.
383 *
384 * @return the strategy used to convert bean type names into element
385 * names. If no element mapper is currently defined then a default one is created.
386 * @deprecated 0.6 use getConfiguration().getElementNameMapper
387 */
388 public NameMapper getElementNameMapper() {
389 return getConfiguration().getElementNameMapper();
390 }
391
392 /***
393 * Sets the strategy used to convert bean type names into element names
394 * @param nameMapper the NameMapper to use for the conversion
395 * @deprecated 0.6 use getConfiguration().setElementNameMapper
396 */
397 public void setElementNameMapper(NameMapper nameMapper) {
398 getConfiguration().setElementNameMapper( nameMapper );
399 }
400
401
402 /***
403 * Gets the name mapping strategy used to convert bean names into attributes.
404 *
405 * @return the strategy used to convert bean type names into attribute
406 * names. If no attributeNamemapper is known, it will default to the ElementNameMapper
407 * @deprecated 0.6 getConfiguration().getAttributeNameMapper
408 */
409 public NameMapper getAttributeNameMapper() {
410 return getConfiguration().getAttributeNameMapper();
411 }
412
413
414 /***
415 * Sets the strategy used to convert bean type names into attribute names
416 * @param nameMapper the NameMapper to use for the convertion
417 * @deprecated 0.6 use getConfiguration().setAttributeNameMapper
418 */
419 public void setAttributeNameMapper(NameMapper nameMapper) {
420 getConfiguration().setAttributeNameMapper( nameMapper );
421 }
422
423 /***
424 * Should the original <code>java.reflect.Introspector</code> bean info search path be used?
425 * By default it will be false.
426 *
427 * @return boolean if the beanInfoSearchPath should be used.
428 * @deprecated 0.6 use getConfiguration().useBeanInfoSearchPath
429 */
430 public boolean useBeanInfoSearchPath() {
431 return getConfiguration().useBeanInfoSearchPath();
432 }
433
434 /***
435 * Specifies if you want to use the beanInfoSearchPath
436 * @see java.beans.Introspector for more details
437 * @param useBeanInfoSearchPath
438 * @deprecated 0.6 use getConfiguration().setUseBeanInfoSearchPath
439 */
440 public void setUseBeanInfoSearchPath(boolean useBeanInfoSearchPath) {
441 getConfiguration().setUseBeanInfoSearchPath( useBeanInfoSearchPath );
442 }
443
444
445
446
447 /***
448 * Flush existing cached <code>XMLBeanInfo</code>'s.
449 *
450 * @deprecated 0.5 use flushable registry instead
451 */
452 public void flushCache() {}
453
454
455 /*** Create a standard <code>XMLBeanInfo</code> by introspection
456 * The actual introspection depends only on the <code>BeanInfo</code>
457 * associated with the bean.
458 *
459 * @param bean introspect this bean
460 * @return XMLBeanInfo describing bean-xml mapping
461 * @throws IntrospectionException when the bean introspection fails
462 */
463 public XMLBeanInfo introspect(Object bean) throws IntrospectionException {
464 if (getLog().isDebugEnabled()) {
465 getLog().debug( "Introspecting..." );
466 getLog().debug(bean);
467 }
468
469 if ( bean instanceof DynaBean ) {
470
471 XMLBeanInfo xmlBeanInfo = findByXMLDescriptor( bean.getClass() );
472 if (xmlBeanInfo != null) {
473 return xmlBeanInfo;
474 }
475
476 return introspect( ((DynaBean) bean).getDynaClass() );
477
478 } else {
479
480 Class normalClass = getClassNormalizer().getNormalizedClass( bean );
481 return introspect( normalClass );
482 }
483 }
484
485 /***
486 * Creates XMLBeanInfo by reading the DynaProperties of a DynaBean.
487 * Customizing DynaBeans using betwixt is not supported.
488 *
489 * @param dynaClass the DynaBean to introspect
490 *
491 * @return XMLBeanInfo for the DynaClass
492 */
493 public XMLBeanInfo introspect(DynaClass dynaClass) {
494
495
496
497
498
499
500
501 XMLBeanInfo xmlInfo = createXMLBeanInfo( dynaClass );
502
503
504 DynaClassBeanType beanClass = new DynaClassBeanType( dynaClass );
505 populate( xmlInfo, beanClass );
506
507 return xmlInfo;
508 }
509
510
511 /***
512 * <p>Introspects the given <code>Class</code> using the dot betwixt
513 * document in the given <code>InputSource</code>.
514 * </p>
515 * <p>
516 * <strong>Note:</strong> that the given mapping will <em>not</em>
517 * be registered by this method. Use {@link #register(Class, InputSource)}
518 * instead.
519 * </p>
520 * @since 0.7
521 * @param aClass <code>Class</code>, not null
522 * @param source <code>InputSource</code>, not null
523 * @return <code>XMLBeanInfo</code> describing the mapping.
524 * @throws SAXException when the input source cannot be parsed
525 * @throws IOException
526 */
527 public synchronized XMLBeanInfo introspect(Class aClass, InputSource source) throws IOException, SAXException {
528
529 configureDigester(aClass);
530 XMLBeanInfo result = (XMLBeanInfo) digester.parse(source);
531 return result;
532 }
533
534
535 /*** Create a standard <code>XMLBeanInfo</code> by introspection.
536 * The actual introspection depends only on the <code>BeanInfo</code>
537 * associated with the bean.
538 *
539 * @param aClass introspect this class
540 * @return XMLBeanInfo describing bean-xml mapping
541 * @throws IntrospectionException when the bean introspection fails
542 */
543 public XMLBeanInfo introspect(Class aClass) throws IntrospectionException {
544
545 String[] searchPath = null;
546 if ( !getConfiguration().useBeanInfoSearchPath() ) {
547 searchPath = Introspector.getBeanInfoSearchPath();
548 Introspector.setBeanInfoSearchPath(new String[] { });
549 }
550
551 XMLBeanInfo xmlInfo = registry.get( aClass );
552
553 if ( xmlInfo == null ) {
554
555 if ( getLog().isDebugEnabled() ) {
556 getLog().debug( "Attempting to lookup an XML descriptor for class: " + aClass );
557 }
558
559 xmlInfo = findByXMLDescriptor( aClass );
560 if ( xmlInfo == null ) {
561 BeanInfo info = Introspector.getBeanInfo( aClass );
562 xmlInfo = introspect( info );
563 }
564
565 if ( xmlInfo != null ) {
566 registry.put( aClass, xmlInfo );
567 }
568 } else {
569 getLog().trace( "Used cached XMLBeanInfo." );
570 }
571
572 if ( getLog().isTraceEnabled() ) {
573 getLog().trace( xmlInfo );
574 }
575 if ( !getConfiguration().useBeanInfoSearchPath() ) {
576
577 Introspector.setBeanInfoSearchPath( searchPath );
578 }
579
580 return xmlInfo;
581 }
582
583 /*** Create a standard <code>XMLBeanInfo</code> by introspection.
584 * The actual introspection depends only on the <code>BeanInfo</code>
585 * associated with the bean.
586 *
587 * @param beanInfo the BeanInfo the xml-bean mapping is based on
588 * @return XMLBeanInfo describing bean-xml mapping
589 * @throws IntrospectionException when the bean introspection fails
590 */
591 public XMLBeanInfo introspect(BeanInfo beanInfo) throws IntrospectionException {
592 XMLBeanInfo xmlBeanInfo = createXMLBeanInfo( beanInfo );
593 populate( xmlBeanInfo, new JavaBeanType( beanInfo ) );
594 return xmlBeanInfo;
595 }
596
597
598 /***
599 * <p>Registers the class mappings specified in the multi-class document
600 * given by the <code>InputSource</code>.
601 * </p>
602 * <p>
603 * <strong>Note:</strong> that this method will override any existing mapping
604 * for the speficied classes.
605 * </p>
606 * @since 0.7
607 * @param source <code>InputSource</code>, not null
608 * @return <code>Class</code> array containing all mapped classes
609 * @throws IntrospectionException
610 * @throws SAXException
611 * @throws IOException
612 */
613 public synchronized Class[] register(InputSource source) throws IntrospectionException, IOException, SAXException {
614 Map xmlBeanInfoByClass = loadMultiMapping(source);
615 Set keySet = xmlBeanInfoByClass.keySet();
616 Class mappedClasses[] = new Class[keySet.size()];
617 int i=0;
618 for (Iterator it=keySet.iterator(); it.hasNext(); ) {
619 Class clazz = (Class) it.next();
620 mappedClasses[i++] = clazz;
621 XMLBeanInfo xmlBeanInfo = (XMLBeanInfo) xmlBeanInfoByClass.get(clazz);
622 if (xmlBeanInfo != null) {
623 getRegistry().put(clazz, xmlBeanInfo);
624 }
625 }
626 return mappedClasses;
627 }
628
629 /***
630 * Loads the multi-mapping from the given <code>InputSource</code>.
631 * @param mapping <code>InputSource</code>, not null
632 * @return <code>Map</code> containing <code>XMLBeanInfo</code>'s
633 * indexes by the <code>Class</code> they describe
634 * @throws IOException
635 * @throws SAXException
636 */
637 private synchronized Map loadMultiMapping(InputSource mapping) throws IOException, SAXException {
638
639
640 if (multiMappingdigester == null) {
641 multiMappingdigester = new MultiMappingBeanInfoDigester();
642 multiMappingdigester.setXMLIntrospector(this);
643 }
644 Map multiBeanInfoMap = (Map) multiMappingdigester.parse(mapping);
645 return multiBeanInfoMap;
646 }
647
648 /***
649 * <p>Registers the class mapping specified in the standard dot-betwixt file.
650 * Subsequent introspections will use this registered mapping for the class.
651 * </p>
652 * <p>
653 * <strong>Note:</strong> that this method will override any existing mapping
654 * for this class.
655 * </p>
656 * @since 0.7
657 * @param aClass <code>Class</code>, not null
658 * @param source <code>InputSource</code>, not null
659 * @throws SAXException when the source cannot be parsed
660 * @throws IOException
661 */
662 public void register(Class aClass, InputSource source) throws IOException, SAXException {
663 XMLBeanInfo xmlBeanInfo = introspect(aClass, source);
664 getRegistry().put(aClass, xmlBeanInfo);
665 }
666
667 /***
668 * Populates the given <code>XMLBeanInfo</code> based on the given type of bean.
669 *
670 * @param xmlBeanInfo populate this, not null
671 * @param bean the type definition for the bean, not null
672 */
673 private void populate(XMLBeanInfo xmlBeanInfo, BeanType bean) {
674 String name = bean.getBeanName();
675
676 ElementDescriptor elementDescriptor = new ElementDescriptor();
677 elementDescriptor.setLocalName(
678 getElementNameMapper().mapTypeToElementName( name ) );
679 elementDescriptor.setPropertyType( bean.getElementType() );
680
681 if (getLog().isTraceEnabled()) {
682 getLog().trace("Populating:" + bean);
683 }
684
685
686 if ( bean.isPrimitiveType() ) {
687 getLog().trace("Bean is primitive");
688 elementDescriptor.setTextExpression( StringExpression.getInstance() );
689
690 } else {
691
692 getLog().trace("Bean is standard type");
693
694 boolean isLoopType = bean.isLoopType();
695
696 List elements = new ArrayList();
697 List attributes = new ArrayList();
698 List contents = new ArrayList();
699
700
701 if ( !( isLoopType && isBasicCollection( bean.getClass() ) ) )
702 {
703 addProperties( bean.getProperties(), elements, attributes, contents );
704 }
705
706
707 if ( isLoopType ) {
708 getLog().trace("Bean is loop");
709 ElementDescriptor loopDescriptor = new ElementDescriptor();
710 loopDescriptor.setCollective(true);
711 loopDescriptor.setContextExpression(
712 new IteratorExpression( EmptyExpression.getInstance() )
713 );
714 if ( bean.isMapType() ) {
715 loopDescriptor.setQualifiedName( "entry" );
716 }
717 elements.add( loopDescriptor );
718 }
719
720 int size = elements.size();
721 if ( size > 0 ) {
722 ElementDescriptor[] descriptors = new ElementDescriptor[size];
723 elements.toArray( descriptors );
724 elementDescriptor.setElementDescriptors( descriptors );
725 }
726 size = attributes.size();
727 if ( size > 0 ) {
728 AttributeDescriptor[] descriptors = new AttributeDescriptor[size];
729 attributes.toArray( descriptors );
730 elementDescriptor.setAttributeDescriptors( descriptors );
731 }
732 size = contents.size();
733 if ( size > 0 ) {
734 if ( size > 0 ) {
735 Descriptor[] descriptors = new Descriptor[size];
736 contents.toArray( descriptors );
737 elementDescriptor.setContentDescriptors( descriptors );
738 }
739 }
740 }
741
742 xmlBeanInfo.setElementDescriptor( elementDescriptor );
743
744
745 defaultAddMethods( elementDescriptor, bean.getElementType() );
746
747 if (getLog().isTraceEnabled()) {
748 getLog().trace("Populated descriptor:");
749 getLog().trace(elementDescriptor);
750 }
751 }
752
753 /***
754 * <p>Is the given type a basic collection?
755 * </p><p>
756 * This is used to determine whether a collective type
757 * should be introspected as a bean (in addition to a collection).
758 * </p>
759 * @param type <code>Class</code>, not null
760 * @return
761 */
762 private boolean isBasicCollection( Class type )
763 {
764 return type.getName().startsWith( "java.util" );
765 }
766
767 /***
768 * Creates XMLBeanInfo for the given DynaClass.
769 *
770 * @param dynaClass the class describing a DynaBean
771 *
772 * @return XMLBeanInfo that describes the properties of the given
773 * DynaClass
774 */
775 protected XMLBeanInfo createXMLBeanInfo(DynaClass dynaClass) {
776
777 XMLBeanInfo beanInfo = new XMLBeanInfo(dynaClass.getClass());
778 return beanInfo;
779 }
780
781
782
783
784 /***
785 * Create a XML descriptor from a bean one.
786 * Go through and work out whether it's a loop property, a primitive or a standard.
787 * The class property is ignored.
788 *
789 * @param propertyDescriptor create a <code>NodeDescriptor</code> for this property
790 * @param useAttributesForPrimitives write primitives as attributes (rather than elements)
791 * @return a correctly configured <code>NodeDescriptor</code> for the property
792 * @throws IntrospectionException when bean introspection fails
793 * @deprecated 0.5 use {@link #createXMLDescriptor}.
794 */
795 public Descriptor createDescriptor(
796 PropertyDescriptor propertyDescriptor,
797 boolean useAttributesForPrimitives
798 ) throws IntrospectionException {
799 return createXMLDescriptor( new BeanProperty( propertyDescriptor ) );
800 }
801
802 /***
803 * Create a XML descriptor from a bean one.
804 * Go through and work out whether it's a loop property, a primitive or a standard.
805 * The class property is ignored.
806 *
807 * @param beanProperty the BeanProperty specifying the property
808 * @return a correctly configured <code>NodeDescriptor</code> for the property
809 * @since 0.5
810 */
811 public Descriptor createXMLDescriptor( BeanProperty beanProperty ) {
812 return beanProperty.createXMLDescriptor( configuration );
813 }
814
815
816 /***
817 * Add any addPropety(PropertyType) methods as Updaters
818 * which are often used for 1-N relationships in beans.
819 * <br>
820 * The tricky part here is finding which ElementDescriptor corresponds
821 * to the method. e.g. a property 'items' might have an Element descriptor
822 * which the method addItem() should match to.
823 * <br>
824 * So the algorithm we'll use
825 * by default is to take the decapitalized name of the property being added
826 * and find the first ElementDescriptor that matches the property starting with
827 * the string. This should work for most use cases.
828 * e.g. addChild() would match the children property.
829 * <br>
830 * TODO this probably needs refactoring. It probably belongs in the bean wrapper
831 * (so that it'll work properly with dyna-beans) and so that the operations can
832 * be optimized by caching. Multiple hash maps are created and getMethods is
833 * called multiple times. This is relatively expensive and so it'd be better
834 * to push into a proper class and cache.
835 * <br>
836 *
837 * @param rootDescriptor add defaults to this descriptor
838 * @param beanClass the <code>Class</code> to which descriptor corresponds
839 */
840 public void defaultAddMethods(
841 ElementDescriptor rootDescriptor,
842 Class beanClass ) {
843
844
845
846
847
848 if ( beanClass != null ) {
849 ArrayList singleParameterAdders = new ArrayList();
850 ArrayList twinParameterAdders = new ArrayList();
851
852 Method[] methods = beanClass.getMethods();
853 for ( int i = 0, size = methods.length; i < size; i++ ) {
854 Method method = methods[i];
855 String name = method.getName();
856 if ( name.startsWith( "add" )) {
857
858
859 Class[] types = method.getParameterTypes();
860 if ( types != null) {
861 if ( getLog().isTraceEnabled() ) {
862 getLog().trace("Searching for match for " + method);
863 }
864
865 switch (types.length)
866 {
867 case 1:
868 singleParameterAdders.add(method);
869 break;
870 case 2:
871 twinParameterAdders.add(method);
872 break;
873 default:
874
875 break;
876 }
877 }
878 }
879 }
880
881 Map elementsByPropertyName = makeElementDescriptorMap( rootDescriptor );
882
883 for (Iterator it=singleParameterAdders.iterator();it.hasNext();) {
884 Method singleParameterAdder = (Method) it.next();
885 setIteratorAdder(elementsByPropertyName, singleParameterAdder);
886 }
887
888 for (Iterator it=twinParameterAdders.iterator();it.hasNext();) {
889 Method twinParameterAdder = (Method) it.next();
890 setMapAdder(elementsByPropertyName, twinParameterAdder);
891 }
892
893
894
895 configureMappingDerivation( rootDescriptor );
896 }
897 }
898
899 /***
900 * Configures the mapping derivation according to the current
901 * <code>MappingDerivationStrategy</code> implementation.
902 * This method acts recursively.
903 * @param rootDescriptor <code>ElementDescriptor</code>, not null
904 */
905 private void configureMappingDerivation(ElementDescriptor descriptor) {
906 boolean useBindTime = getConfiguration().getMappingDerivationStrategy()
907 .useBindTimeTypeForMapping(descriptor.getPropertyType(), descriptor.getSingularPropertyType());
908 descriptor.setUseBindTimeTypeForMapping(useBindTime);
909 ElementDescriptor[] childDescriptors = descriptor.getElementDescriptors();
910 for (int i=0, size=childDescriptors.length; i<size; i++) {
911 configureMappingDerivation(childDescriptors[i]);
912 }
913 }
914
915 /***
916 * Sets the adder method where the corresponding property is an iterator
917 * @param rootDescriptor
918 * @param singleParameterAdder
919 */
920 private void setIteratorAdder(
921 Map elementsByPropertyName,
922 Method singleParameterAdderMethod) {
923
924 String adderName = singleParameterAdderMethod.getName();
925 String propertyName = Introspector.decapitalize(adderName.substring(3));
926 ElementDescriptor matchingDescriptor = getMatchForAdder(propertyName, elementsByPropertyName);
927 if (matchingDescriptor != null) {
928
929
930 Class singularType = singleParameterAdderMethod.getParameterTypes()[0];
931 if (getLog().isTraceEnabled()) {
932 getLog().trace(adderName + "->" + propertyName);
933 }
934
935 getLog().trace("Matching collection or iteration");
936
937 matchingDescriptor.setUpdater( new MethodUpdater( singleParameterAdderMethod ) );
938 matchingDescriptor.setSingularPropertyType( singularType );
939 matchingDescriptor.setHollow(!isPrimitiveType(singularType));
940 String localName = matchingDescriptor.getLocalName();
941 if ( localName == null || localName.length() == 0 ) {
942 matchingDescriptor.setLocalName(
943 getElementNameMapper()
944 .mapTypeToElementName( propertyName ) );
945 }
946
947 if ( getLog().isDebugEnabled() ) {
948 getLog().debug( "!! " + singleParameterAdderMethod);
949 getLog().debug( "!! " + singularType);
950 }
951 }
952 }
953
954 /***
955 * Sets the adder where the corresponding property type is an map
956 * @param rootDescriptor
957 * @param singleParameterAdder
958 */
959 private void setMapAdder(
960 Map elementsByPropertyName,
961 Method twinParameterAdderMethod) {
962 String adderName = twinParameterAdderMethod.getName();
963 String propertyName = Introspector.decapitalize(adderName.substring(3));
964 ElementDescriptor matchingDescriptor = getMatchForAdder(propertyName, elementsByPropertyName);
965 if ( matchingDescriptor != null
966 && Map.class.isAssignableFrom( matchingDescriptor.getPropertyType() )) {
967
968 getLog().trace("Matching map");
969 ElementDescriptor[] children
970 = matchingDescriptor.getElementDescriptors();
971
972 if ( children.length == 0 ) {
973 getLog().info(
974 "'entry' descriptor is missing for map. "
975 + "Updaters cannot be set");
976
977 } else {
978 Class[] types = twinParameterAdderMethod.getParameterTypes();
979 Class keyType = types[0];
980 Class valueType = types[1];
981
982
983
984 MapEntryAdder adder = new MapEntryAdder(twinParameterAdderMethod);
985 for (
986 int n=0,
987 noOfGrandChildren = children.length;
988 n < noOfGrandChildren;
989 n++ ) {
990 if ( "key".equals( children[n].getLocalName() ) ) {
991
992 children[n].setUpdater( adder.getKeyUpdater() );
993 children[n].setSingularPropertyType( keyType );
994 if (children[n].getPropertyType() == null) {
995 children[n].setPropertyType( valueType );
996 }
997 if ( isPrimitiveType(keyType) ) {
998 children[n].setHollow(false);
999 }
1000 if ( getLog().isTraceEnabled() ) {
1001 getLog().trace( "Key descriptor: " + children[n]);
1002 }
1003
1004 } else if ( "value".equals( children[n].getLocalName() ) ) {
1005
1006 children[n].setUpdater( adder.getValueUpdater() );
1007 children[n].setSingularPropertyType( valueType );
1008 if (children[n].getPropertyType() == null) {
1009 children[n].setPropertyType( valueType );
1010 }
1011 if ( isPrimitiveType( valueType) ) {
1012 children[n].setHollow(false);
1013 }
1014 if ( isLoopType( valueType )) {
1015
1016
1017
1018 ElementDescriptor loopDescriptor = new ElementDescriptor();
1019 loopDescriptor.setHollow(true);
1020 loopDescriptor.setSingularPropertyType( valueType );
1021 loopDescriptor.setPropertyType( valueType );
1022 children[n].addElementDescriptor(loopDescriptor);
1023 loopDescriptor.setCollective(true);
1024 }
1025 if ( getLog().isTraceEnabled() ) {
1026 getLog().trace( "Value descriptor: " + children[n]);
1027 }
1028 }
1029 }
1030 }
1031 }
1032 }
1033
1034 /***
1035 * Gets an ElementDescriptor for the property matching the adder
1036 * @param adderName
1037 * @param rootDescriptor
1038 * @return
1039 */
1040 private ElementDescriptor getMatchForAdder(
1041 String propertyName,
1042 Map elementsByPropertyName) {
1043 ElementDescriptor matchingDescriptor = null;
1044 if (propertyName.length() > 0) {
1045 if ( getLog().isTraceEnabled() ) {
1046 getLog().trace( "findPluralDescriptor( " + propertyName
1047 + " ):root property name=" + propertyName );
1048 }
1049
1050 PluralStemmer stemmer = getPluralStemmer();
1051 matchingDescriptor = stemmer.findPluralDescriptor( propertyName, elementsByPropertyName );
1052
1053 if ( getLog().isTraceEnabled() ) {
1054 getLog().trace(
1055 "findPluralDescriptor( " + propertyName
1056 + " ):ElementDescriptor=" + matchingDescriptor );
1057 }
1058 }
1059 return matchingDescriptor;
1060 }
1061
1062
1063
1064
1065
1066 /***
1067 * Creates a map where the keys are the property names and the values are the ElementDescriptors
1068 */
1069 private Map makeElementDescriptorMap( ElementDescriptor rootDescriptor ) {
1070 Map result = new HashMap();
1071 String rootPropertyName = rootDescriptor.getPropertyName();
1072 if (rootPropertyName != null) {
1073 result.put(rootPropertyName, rootDescriptor);
1074 }
1075 makeElementDescriptorMap( rootDescriptor, result );
1076 return result;
1077 }
1078
1079 /***
1080 * Creates a map where the keys are the property names and the values are the ElementDescriptors
1081 *
1082 * @param rootDescriptor the values of the maps are the children of this
1083 * <code>ElementDescriptor</code> index by their property names
1084 * @param map the map to which the elements will be added
1085 */
1086 private void makeElementDescriptorMap( ElementDescriptor rootDescriptor, Map map ) {
1087 ElementDescriptor[] children = rootDescriptor.getElementDescriptors();
1088 if ( children != null ) {
1089 for ( int i = 0, size = children.length; i < size; i++ ) {
1090 ElementDescriptor child = children[i];
1091 String propertyName = child.getPropertyName();
1092 if ( propertyName != null ) {
1093 map.put( propertyName, child );
1094 }
1095 makeElementDescriptorMap( child, map );
1096 }
1097 }
1098 }
1099
1100 /***
1101 * A Factory method to lazily create a new strategy
1102 * to detect matching singular and plural properties.
1103 *
1104 * @return new defualt PluralStemmer implementation
1105 * @deprecated 0.6 this method has been moved into IntrospectionConfiguration.
1106 * Those who need to vary this should subclass that class instead
1107 */
1108 protected PluralStemmer createPluralStemmer() {
1109 return new DefaultPluralStemmer();
1110 }
1111
1112 /***
1113 * A Factory method to lazily create a strategy
1114 * used to convert bean type names into element names.
1115 *
1116 * @return new default NameMapper implementation
1117 * @deprecated 0.6 this method has been moved into IntrospectionConfiguration.
1118 * Those who need to vary this should subclass that class instead
1119 */
1120 protected NameMapper createNameMapper() {
1121 return new DefaultNameMapper();
1122 }
1123
1124 /***
1125 * Attempt to lookup the XML descriptor for the given class using the
1126 * classname + ".betwixt" using the same ClassLoader used to load the class
1127 * or return null if it could not be loaded
1128 *
1129 * @param aClass digester .betwixt file for this class
1130 * @return XMLBeanInfo digested from the .betwixt file if one can be found.
1131 * Otherwise null.
1132 */
1133 protected synchronized XMLBeanInfo findByXMLDescriptor( Class aClass ) {
1134
1135 String name = aClass.getName();
1136 int idx = name.lastIndexOf( '.' );
1137 if ( idx >= 0 ) {
1138 name = name.substring( idx + 1 );
1139 }
1140 name += ".betwixt";
1141
1142 URL url = aClass.getResource( name );
1143 if ( url != null ) {
1144 try {
1145 String urlText = url.toString();
1146 if ( getLog().isDebugEnabled( )) {
1147 getLog().debug( "Parsing Betwixt XML descriptor: " + urlText );
1148 }
1149
1150
1151 configureDigester(aClass);
1152 return (XMLBeanInfo) digester.parse( urlText );
1153 } catch (Exception e) {
1154 getLog().warn( "Caught exception trying to parse: " + name, e );
1155 }
1156 }
1157
1158 if ( getLog().isTraceEnabled() ) {
1159 getLog().trace( "Could not find betwixt file " + name );
1160 }
1161 return null;
1162 }
1163
1164 /***
1165 * Configures the single <code>Digester</code> instance used by this introspector.
1166 * @param aClass <code>Class</code>, not null
1167 */
1168 private synchronized void configureDigester(Class aClass) {
1169 if ( digester == null ) {
1170 digester = new XMLBeanInfoDigester();
1171 digester.setXMLIntrospector( this );
1172 }
1173 digester.setBeanClass( aClass );
1174 }
1175
1176 /***
1177 * Loop through properties and process each one
1178 *
1179 * @param beanInfo the BeanInfo whose properties will be processed
1180 * @param elements ElementDescriptor list to which elements will be added
1181 * @param attributes AttributeDescriptor list to which attributes will be added
1182 * @param contents Descriptor list to which mixed content will be added
1183 * @throws IntrospectionException if the bean introspection fails
1184 * @deprecated 0.5 use {@link #addProperties(BeanProperty[], List, List,List)}
1185 */
1186 protected void addProperties(
1187 BeanInfo beanInfo,
1188 List elements,
1189 List attributes,
1190 List contents)
1191 throws
1192 IntrospectionException {
1193 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
1194 if ( descriptors != null ) {
1195 for ( int i = 0, size = descriptors.length; i < size; i++ ) {
1196 addProperty(beanInfo, descriptors[i], elements, attributes, contents);
1197 }
1198 }
1199 if (getLog().isTraceEnabled()) {
1200 getLog().trace(elements);
1201 getLog().trace(attributes);
1202 getLog().trace(contents);
1203 }
1204 }
1205 /***
1206 * Loop through properties and process each one
1207 *
1208 * @param beanProperties the properties to be processed
1209 * @param elements ElementDescriptor list to which elements will be added
1210 * @param attributes AttributeDescriptor list to which attributes will be added
1211 * @param contents Descriptor list to which mixed content will be added
1212 * @since 0.5
1213 */
1214 protected void addProperties(
1215 BeanProperty[] beanProperties,
1216 List elements,
1217 List attributes,
1218 List contents) {
1219 if ( beanProperties != null ) {
1220 if (getLog().isTraceEnabled()) {
1221 getLog().trace(beanProperties.length + " properties to be added");
1222 }
1223 for ( int i = 0, size = beanProperties.length; i < size; i++ ) {
1224 addProperty(beanProperties[i], elements, attributes, contents);
1225 }
1226 }
1227 if (getLog().isTraceEnabled()) {
1228 getLog().trace("After properties have been added (elements, attributes, contents):");
1229 getLog().trace(elements);
1230 getLog().trace(attributes);
1231 getLog().trace(contents);
1232 }
1233 }
1234
1235
1236 /***
1237 * Process a property.
1238 * Go through and work out whether it's a loop property, a primitive or a standard.
1239 * The class property is ignored.
1240 *
1241 * @param beanInfo the BeanInfo whose property is being processed
1242 * @param propertyDescriptor the PropertyDescriptor to process
1243 * @param elements ElementDescriptor list to which elements will be added
1244 * @param attributes AttributeDescriptor list to which attributes will be added
1245 * @param contents Descriptor list to which mixed content will be added
1246 * @throws IntrospectionException if the bean introspection fails
1247 * @deprecated 0.5 BeanInfo is no longer required.
1248 * Use {@link #addProperty(PropertyDescriptor, List, List, List)} instead.
1249 */
1250 protected void addProperty(
1251 BeanInfo beanInfo,
1252 PropertyDescriptor propertyDescriptor,
1253 List elements,
1254 List attributes,
1255 List contents)
1256 throws
1257 IntrospectionException {
1258 addProperty( propertyDescriptor, elements, attributes, contents);
1259 }
1260
1261 /***
1262 * Process a property.
1263 * Go through and work out whether it's a loop property, a primitive or a standard.
1264 * The class property is ignored.
1265 *
1266 * @param propertyDescriptor the PropertyDescriptor to process
1267 * @param elements ElementDescriptor list to which elements will be added
1268 * @param attributes AttributeDescriptor list to which attributes will be added
1269 * @param contents Descriptor list to which mixed content will be added
1270 * @throws IntrospectionException if the bean introspection fails
1271 * @deprecated 0.5 use {@link #addProperty(BeanProperty, List, List, List)} instead
1272 */
1273 protected void addProperty(
1274 PropertyDescriptor propertyDescriptor,
1275 List elements,
1276 List attributes,
1277 List contents)
1278 throws
1279 IntrospectionException {
1280 addProperty(new BeanProperty( propertyDescriptor ), elements, attributes, contents);
1281 }
1282
1283 /***
1284 * Process a property.
1285 * Go through and work out whether it's a loop property, a primitive or a standard.
1286 * The class property is ignored.
1287 *
1288 * @param beanProperty the bean property to process
1289 * @param elements ElementDescriptor list to which elements will be added
1290 * @param attributes AttributeDescriptor list to which attributes will be added
1291 * @param contents Descriptor list to which mixed content will be added
1292 * @since 0.5
1293 */
1294 protected void addProperty(
1295 BeanProperty beanProperty,
1296 List elements,
1297 List attributes,
1298 List contents) {
1299 Descriptor nodeDescriptor = createXMLDescriptor(beanProperty);
1300 if (nodeDescriptor == null) {
1301 return;
1302 }
1303 if (nodeDescriptor instanceof ElementDescriptor) {
1304 elements.add(nodeDescriptor);
1305 } else if (nodeDescriptor instanceof AttributeDescriptor) {
1306 attributes.add(nodeDescriptor);
1307 } else {
1308 contents.add(nodeDescriptor);
1309 }
1310 }
1311
1312 /***
1313 * Loop through properties and process each one
1314 *
1315 * @param beanInfo the BeanInfo whose properties will be processed
1316 * @param elements ElementDescriptor list to which elements will be added
1317 * @param attributes AttributeDescriptor list to which attributes will be added
1318 * @throws IntrospectionException if the bean introspection fails
1319 * @deprecated 0.5 this method does not support mixed content.
1320 * Use {@link #addProperties(BeanInfo, List, List, List)} instead.
1321 */
1322 protected void addProperties(
1323 BeanInfo beanInfo,
1324 List elements,
1325 List attributes)
1326 throws
1327 IntrospectionException {
1328 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
1329 if ( descriptors != null ) {
1330 for ( int i = 0, size = descriptors.length; i < size; i++ ) {
1331 addProperty(beanInfo, descriptors[i], elements, attributes);
1332 }
1333 }
1334 if (getLog().isTraceEnabled()) {
1335 getLog().trace(elements);
1336 getLog().trace(attributes);
1337 }
1338 }
1339
1340 /***
1341 * Process a property.
1342 * Go through and work out whether it's a loop property, a primitive or a standard.
1343 * The class property is ignored.
1344 *
1345 * @param beanInfo the BeanInfo whose property is being processed
1346 * @param propertyDescriptor the PropertyDescriptor to process
1347 * @param elements ElementDescriptor list to which elements will be added
1348 * @param attributes AttributeDescriptor list to which attributes will be added
1349 * @throws IntrospectionException if the bean introspection fails
1350 * @deprecated 0.5 this method does not support mixed content.
1351 * Use {@link #addProperty(BeanInfo, PropertyDescriptor, List, List, List)} instead.
1352 */
1353 protected void addProperty(
1354 BeanInfo beanInfo,
1355 PropertyDescriptor propertyDescriptor,
1356 List elements,
1357 List attributes)
1358 throws
1359 IntrospectionException {
1360 NodeDescriptor nodeDescriptor = XMLIntrospectorHelper
1361 .createDescriptor(propertyDescriptor,
1362 isAttributesForPrimitives(),
1363 this);
1364 if (nodeDescriptor == null) {
1365 return;
1366 }
1367 if (nodeDescriptor instanceof ElementDescriptor) {
1368 elements.add(nodeDescriptor);
1369 } else {
1370 attributes.add(nodeDescriptor);
1371 }
1372 }
1373
1374
1375 /***
1376 * Factory method to create XMLBeanInfo instances
1377 *
1378 * @param beanInfo the BeanInfo from which the XMLBeanInfo will be created
1379 * @return XMLBeanInfo describing the bean-xml mapping
1380 */
1381 protected XMLBeanInfo createXMLBeanInfo( BeanInfo beanInfo ) {
1382 XMLBeanInfo xmlBeanInfo = new XMLBeanInfo( beanInfo.getBeanDescriptor().getBeanClass() );
1383 return xmlBeanInfo;
1384 }
1385
1386 /***
1387 * Is this class a loop?
1388 *
1389 * @param type the Class to test
1390 * @return true if the type is a loop type
1391 */
1392 public boolean isLoopType(Class type) {
1393 return getConfiguration().isLoopType(type);
1394 }
1395
1396
1397 /***
1398 * Is this class a primitive?
1399 *
1400 * @param type the Class to test
1401 * @return true for primitive types
1402 */
1403 public boolean isPrimitiveType(Class type) {
1404
1405
1406 TypeBindingStrategy.BindingType bindingType
1407 = configuration.getTypeBindingStrategy().bindingType( type ) ;
1408 boolean result = (bindingType.equals(TypeBindingStrategy.BindingType.PRIMITIVE));
1409 return result;
1410 }
1411
1412
1413 /*** Some type of pseudo-bean */
1414 private abstract class BeanType {
1415 /***
1416 * Gets the name for this bean type
1417 * @return the bean type name, not null
1418 */
1419 public abstract String getBeanName();
1420
1421 /***
1422 * Gets the type to be used by the associated element
1423 * @return a Class that is the type not null
1424 */
1425 public abstract Class getElementType();
1426
1427 /***
1428 * Is this type a primitive?
1429 * @return true if this type should be treated by betwixt as a primitive
1430 */
1431 public abstract boolean isPrimitiveType();
1432
1433 /***
1434 * is this type a map?
1435 * @return true this should be treated as a map.
1436 */
1437 public abstract boolean isMapType();
1438
1439 /***
1440 * Is this type a loop?
1441 * @return true if this should be treated as a loop
1442 */
1443 public abstract boolean isLoopType();
1444
1445 /***
1446 * Gets the properties associated with this bean.
1447 * @return the BeanProperty's, not null
1448 */
1449 public abstract BeanProperty[] getProperties();
1450
1451 /***
1452 * Create string representation
1453 * @return something useful for logging
1454 */
1455 public String toString() {
1456 return "Bean[name=" + getBeanName() + ", type=" + getElementType();
1457 }
1458 }
1459
1460 /*** Supports standard Java Beans */
1461 private class JavaBeanType extends BeanType {
1462 /*** Introspected bean */
1463 private BeanInfo beanInfo;
1464 /*** Bean class */
1465 private Class beanClass;
1466 /*** Bean name */
1467 private String name;
1468 /*** Bean properties */
1469 private BeanProperty[] properties;
1470
1471 /***
1472 * Constructs a BeanType for a standard Java Bean
1473 * @param beanInfo the BeanInfo describing the standard Java Bean, not null
1474 */
1475 public JavaBeanType(BeanInfo beanInfo) {
1476 this.beanInfo = beanInfo;
1477 BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
1478 beanClass = beanDescriptor.getBeanClass();
1479 name = beanDescriptor.getName();
1480
1481 if (beanClass.isArray()) {
1482
1483 name = "Array";
1484 }
1485
1486 }
1487
1488 /*** @see BeanType #getElementType */
1489 public Class getElementType() {
1490 return beanClass;
1491 }
1492
1493 /*** @see BeanType#getBeanName */
1494 public String getBeanName() {
1495 return name;
1496 }
1497
1498 /*** @see BeanType#isPrimitiveType */
1499 public boolean isPrimitiveType() {
1500 return XMLIntrospector.this.isPrimitiveType( beanClass );
1501 }
1502
1503 /*** @see BeanType#isLoopType */
1504 public boolean isLoopType() {
1505 return getConfiguration().isLoopType( beanClass );
1506 }
1507
1508 /*** @see BeanType#isMapType */
1509 public boolean isMapType() {
1510 return Map.class.isAssignableFrom( beanClass );
1511 }
1512
1513 /*** @see BeanType#getProperties */
1514 public BeanProperty[] getProperties() {
1515
1516 if ( properties == null ) {
1517 ArrayList propertyDescriptors = new ArrayList();
1518
1519 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
1520 if ( descriptors != null ) {
1521 for (int i=0, size=descriptors.length; i<size; i++) {
1522 if (!getConfiguration().getPropertySuppressionStrategy()
1523 .suppressProperty(
1524 beanClass,
1525 descriptors[i].getPropertyType(),
1526 descriptors[i].getName())) {
1527 propertyDescriptors.add( descriptors[i] );
1528 }
1529 }
1530 }
1531
1532
1533 BeanInfo[] additionals = beanInfo.getAdditionalBeanInfo();
1534 if ( additionals != null ) {
1535 for ( int i=0, outerSize=additionals.length; i<outerSize; i++ ) {
1536 BeanInfo additionalInfo = additionals[i];
1537 descriptors = beanInfo.getPropertyDescriptors();
1538 if ( descriptors != null ) {
1539 for (int j=0, innerSize=descriptors.length; j<innerSize; j++) {
1540 if (!getConfiguration().getPropertySuppressionStrategy()
1541 .suppressProperty(
1542 beanClass,
1543 descriptors[j].getPropertyType(),
1544 descriptors[j].getName())) {
1545 propertyDescriptors.add( descriptors[j] );
1546 }
1547 }
1548 }
1549 }
1550 }
1551
1552 addAllSuperinterfaces(beanClass, propertyDescriptors);
1553
1554
1555 properties = new BeanProperty[ propertyDescriptors.size() ];
1556 int count = 0;
1557 for ( Iterator it = propertyDescriptors.iterator(); it.hasNext(); count++) {
1558 PropertyDescriptor propertyDescriptor = (PropertyDescriptor) it.next();
1559 properties[count] = new BeanProperty( propertyDescriptor );
1560 }
1561 }
1562 return properties;
1563 }
1564
1565 /***
1566 * Adds all super interfaces.
1567 * Super interface methods are not returned within the usual
1568 * bean info for an interface.
1569 * @param clazz <code>Class</code>, not null
1570 * @param propertyDescriptors <code>ArrayList</code> of <code>PropertyDescriptor</code>s', not null
1571 */
1572 private void addAllSuperinterfaces(Class clazz, ArrayList propertyDescriptors) {
1573 if (clazz.isInterface()) {
1574 Class[] superinterfaces = clazz.getInterfaces();
1575 for (int i=0, size=superinterfaces.length; i<size; i++) {
1576 try {
1577
1578 BeanInfo beanInfo = Introspector.getBeanInfo(superinterfaces[i]);
1579 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
1580 for (int j=0, descriptorLength=descriptors.length; j<descriptorLength ; j++) {
1581 if (!getConfiguration().getPropertySuppressionStrategy()
1582 .suppressProperty(
1583 beanClass,
1584 descriptors[j].getPropertyType(),
1585 descriptors[j].getName())) {
1586 propertyDescriptors.add( descriptors[j] );
1587 }
1588 }
1589 addAllSuperinterfaces(superinterfaces[i], propertyDescriptors);
1590
1591 } catch (IntrospectionException ex) {
1592 log.info("Introspection on superinterface failed.", ex);
1593 }
1594 }
1595 }
1596 }
1597
1598 }
1599
1600 /*** Implementation for DynaClasses */
1601 private class DynaClassBeanType extends BeanType {
1602 /*** BeanType for this DynaClass */
1603 private DynaClass dynaClass;
1604 /*** Properties extracted in constuctor */
1605 private BeanProperty[] properties;
1606
1607 /***
1608 * Constructs a BeanType for a DynaClass
1609 * @param dynaClass not null
1610 */
1611 public DynaClassBeanType(DynaClass dynaClass) {
1612 this.dynaClass = dynaClass;
1613 DynaProperty[] dynaProperties = dynaClass.getDynaProperties();
1614 properties = new BeanProperty[dynaProperties.length];
1615 for (int i=0, size=dynaProperties.length; i<size; i++) {
1616 properties[i] = new BeanProperty(dynaProperties[i]);
1617 }
1618 }
1619
1620 /*** @see BeanType#getBeanName */
1621 public String getBeanName() {
1622 return dynaClass.getName();
1623 }
1624 /*** @see BeanType#getElementType */
1625 public Class getElementType() {
1626 return DynaClass.class;
1627 }
1628 /*** @see BeanType#isPrimitiveType */
1629 public boolean isPrimitiveType() {
1630 return false;
1631 }
1632 /*** @see BeanType#isMapType */
1633 public boolean isMapType() {
1634 return false;
1635 }
1636 /*** @see BeanType#isLoopType */
1637 public boolean isLoopType() {
1638 return false;
1639 }
1640 /*** @see BeanType#getProperties */
1641 public BeanProperty[] getProperties() {
1642 return properties;
1643 }
1644 }
1645 }