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