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