1 package org.apache.commons.betwixt;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one or more
5 * contributor license agreements. See the NOTICE file distributed with
6 * this work for additional information regarding copyright ownership.
7 * The ASF licenses this file to You under the Apache License, Version 2.0
8 * (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
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 // Properties
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 // Methods
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 // allow DynaBean implementations to be overridden by .betwixt files
473 XMLBeanInfo xmlBeanInfo = findByXMLDescriptor( bean.getClass() );
474 if (xmlBeanInfo != null) {
475 return xmlBeanInfo;
476 }
477 // this is DynaBean use the DynaClass for introspection
478 return introspect( ((DynaBean) bean).getDynaClass() );
479
480 } else {
481 // normal bean so normal introspection
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 // for now this method does not do much, since XMLBeanInfoRegistry cannot
498 // use a DynaClass as a key
499 // TODO: add caching for DynaClass XMLBeanInfo
500 // need to work out if this is possible
501
502 // this line allows subclasses to change creation strategy
503 XMLBeanInfo xmlInfo = createXMLBeanInfo( dynaClass );
504
505 // populate the created info with
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 // need to synchronize since we only use one instance and SAX is essentially one thread only
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 // we first reset the beaninfo searchpath.
547 String[] searchPath = null;
548 if ( !getConfiguration().useBeanInfoSearchPath() ) {
549 try {
550 searchPath = Introspector.getBeanInfoSearchPath();
551 Introspector.setBeanInfoSearchPath(new String[] { });
552 } catch (SecurityException e) {
553 // this call may fail in some environments
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 // lets see if we can find an XML descriptor first
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 // we restore the beaninfo searchpath.
593 Introspector.setBeanInfoSearchPath( searchPath );
594 } catch (SecurityException e) {
595 // this call may fail in some environments
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 // synchronized method so this digester is only used by
660 // one thread at once
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 // add default string value for primitive types
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 // add bean properties for all collection which are not basic
722 if ( !( isLoopType && isBasicCollection( bean.getClass() ) ) )
723 {
724 addProperties( bean.getProperties(), elements, attributes, contents );
725 }
726
727 // add iterator for collections
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 // default any addProperty() methods
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 // XXX is the chosen class right?
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 // TODO: this probably does work properly with DynaBeans: need to push
898 // implementation into an class and expose it on BeanType.
899
900 // lets iterate over all methods looking for one of the form
901 // add*(PropertyType)
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 // TODO: should we filter out non-void returning methods?
912 // some beans will return something as a helper
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 // ignore
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 // need to call this once all the defaults have been added
948 // so that all the singular types have been set correctly
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 //TODO defensive code: probably should check descriptor type
984
985 Class singularType = singleParameterAdderMethod.getParameterTypes()[0];
986 if (getLog().isTraceEnabled()) {
987 getLog().trace(adderName + "->" + propertyName);
988 }
989 // this may match a standard collection or iteration
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 // this may match a map
1033 getLog().trace("Matching map");
1034 ElementDescriptor[] children
1035 = matchingDescriptor.getElementDescriptors();
1036 // see if the descriptor's been set up properly
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 // loop through children
1059 // adding updaters for key and value
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 // need to attach a hollow descriptor
1092 // don't know the element name
1093 // so use null name (to match anything)
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 // Implementation methods
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 // trim the package name
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 // synchronized method so this digester is only used by
1224 // one thread at once
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
1248 if (configuration.isUseContextClassLoader()) {
1249 // Use the context classloader to find classes.
1250 //
1251 // There is one case in which this gives odd behaviour; with digester <= 1.8 (at least),
1252 // if the context classloader is inaccessable for some reason then Digester will fall
1253 // back to using the same classloader that loaded Digester.
1254 digester.setUseContextClassLoader(true);
1255 } else {
1256 // Use the classloader that loaded this betwixt library, regardless
1257 // of where the Digester class library happens to be.
1258 digester.setClassLoader(this.getClass().getClassLoader());
1259 }
1260
1261 digester.setBeanClass( aClass );
1262 }
1263
1264 /**
1265 * Loop through properties and process each one
1266 *
1267 * @param beanInfo the BeanInfo whose properties will be processed
1268 * @param elements ElementDescriptor list to which elements will be added
1269 * @param attributes AttributeDescriptor list to which attributes will be added
1270 * @param contents Descriptor list to which mixed content will be added
1271 * @throws IntrospectionException if the bean introspection fails
1272 * @deprecated 0.5 use {@link #addProperties(BeanProperty[], List, List,List)}
1273 */
1274 protected void addProperties(
1275 BeanInfo beanInfo,
1276 List elements,
1277 List attributes,
1278 List contents)
1279 throws
1280 IntrospectionException {
1281 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
1282 if ( descriptors != null ) {
1283 for ( int i = 0, size = descriptors.length; i < size; i++ ) {
1284 addProperty(beanInfo, descriptors[i], elements, attributes, contents);
1285 }
1286 }
1287 if (getLog().isTraceEnabled()) {
1288 getLog().trace(elements);
1289 getLog().trace(attributes);
1290 getLog().trace(contents);
1291 }
1292 }
1293 /**
1294 * Loop through properties and process each one
1295 *
1296 * @param beanProperties the properties to be processed
1297 * @param elements ElementDescriptor list to which elements will be added
1298 * @param attributes AttributeDescriptor list to which attributes will be added
1299 * @param contents Descriptor list to which mixed content will be added
1300 * @since 0.5
1301 */
1302 protected void addProperties(
1303 BeanProperty[] beanProperties,
1304 List elements,
1305 List attributes,
1306 List contents) {
1307 if ( beanProperties != null ) {
1308 if (getLog().isTraceEnabled()) {
1309 getLog().trace(beanProperties.length + " properties to be added");
1310 }
1311 for ( int i = 0, size = beanProperties.length; i < size; i++ ) {
1312 addProperty(beanProperties[i], elements, attributes, contents);
1313 }
1314 }
1315 if (getLog().isTraceEnabled()) {
1316 getLog().trace("After properties have been added (elements, attributes, contents):");
1317 getLog().trace(elements);
1318 getLog().trace(attributes);
1319 getLog().trace(contents);
1320 }
1321 }
1322
1323
1324 /**
1325 * Process a property.
1326 * Go through and work out whether it's a loop property, a primitive or a standard.
1327 * The class property is ignored.
1328 *
1329 * @param beanInfo the BeanInfo whose property is being processed
1330 * @param propertyDescriptor the PropertyDescriptor to process
1331 * @param elements ElementDescriptor list to which elements will be added
1332 * @param attributes AttributeDescriptor list to which attributes will be added
1333 * @param contents Descriptor list to which mixed content will be added
1334 * @throws IntrospectionException if the bean introspection fails
1335 * @deprecated 0.5 BeanInfo is no longer required.
1336 * Use {@link #addProperty(PropertyDescriptor, List, List, List)} instead.
1337 */
1338 protected void addProperty(
1339 BeanInfo beanInfo,
1340 PropertyDescriptor propertyDescriptor,
1341 List elements,
1342 List attributes,
1343 List contents)
1344 throws
1345 IntrospectionException {
1346 addProperty( propertyDescriptor, elements, attributes, contents);
1347 }
1348
1349 /**
1350 * Process a property.
1351 * Go through and work out whether it's a loop property, a primitive or a standard.
1352 * The class property is ignored.
1353 *
1354 * @param propertyDescriptor the PropertyDescriptor to process
1355 * @param elements ElementDescriptor list to which elements will be added
1356 * @param attributes AttributeDescriptor list to which attributes will be added
1357 * @param contents Descriptor list to which mixed content will be added
1358 * @throws IntrospectionException if the bean introspection fails
1359 * @deprecated 0.5 use {@link #addProperty(BeanProperty, List, List, List)} instead
1360 */
1361 protected void addProperty(
1362 PropertyDescriptor propertyDescriptor,
1363 List elements,
1364 List attributes,
1365 List contents)
1366 throws
1367 IntrospectionException {
1368 addProperty(new BeanProperty( propertyDescriptor ), elements, attributes, contents);
1369 }
1370
1371 /**
1372 * Process a property.
1373 * Go through and work out whether it's a loop property, a primitive or a standard.
1374 * The class property is ignored.
1375 *
1376 * @param beanProperty the bean property to process
1377 * @param elements ElementDescriptor list to which elements will be added
1378 * @param attributes AttributeDescriptor list to which attributes will be added
1379 * @param contents Descriptor list to which mixed content will be added
1380 * @since 0.5
1381 */
1382 protected void addProperty(
1383 BeanProperty beanProperty,
1384 List elements,
1385 List attributes,
1386 List contents) {
1387 Descriptor nodeDescriptor = createXMLDescriptor(beanProperty);
1388 if (nodeDescriptor == null) {
1389 return;
1390 }
1391 if (nodeDescriptor instanceof ElementDescriptor) {
1392 elements.add(nodeDescriptor);
1393 } else if (nodeDescriptor instanceof AttributeDescriptor) {
1394 attributes.add(nodeDescriptor);
1395 } else {
1396 contents.add(nodeDescriptor);
1397 }
1398 }
1399
1400 /**
1401 * Loop through properties and process each one
1402 *
1403 * @param beanInfo the BeanInfo whose properties will be processed
1404 * @param elements ElementDescriptor list to which elements will be added
1405 * @param attributes AttributeDescriptor list to which attributes will be added
1406 * @throws IntrospectionException if the bean introspection fails
1407 * @deprecated 0.5 this method does not support mixed content.
1408 * Use {@link #addProperties(BeanInfo, List, List, List)} instead.
1409 */
1410 protected void addProperties(
1411 BeanInfo beanInfo,
1412 List elements,
1413 List attributes)
1414 throws
1415 IntrospectionException {
1416 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
1417 if ( descriptors != null ) {
1418 for ( int i = 0, size = descriptors.length; i < size; i++ ) {
1419 addProperty(beanInfo, descriptors[i], elements, attributes);
1420 }
1421 }
1422 if (getLog().isTraceEnabled()) {
1423 getLog().trace(elements);
1424 getLog().trace(attributes);
1425 }
1426 }
1427
1428 /**
1429 * Process a property.
1430 * Go through and work out whether it's a loop property, a primitive or a standard.
1431 * The class property is ignored.
1432 *
1433 * @param beanInfo the BeanInfo whose property is being processed
1434 * @param propertyDescriptor the PropertyDescriptor to process
1435 * @param elements ElementDescriptor list to which elements will be added
1436 * @param attributes AttributeDescriptor list to which attributes will be added
1437 * @throws IntrospectionException if the bean introspection fails
1438 * @deprecated 0.5 this method does not support mixed content.
1439 * Use {@link #addProperty(BeanInfo, PropertyDescriptor, List, List, List)} instead.
1440 */
1441 protected void addProperty(
1442 BeanInfo beanInfo,
1443 PropertyDescriptor propertyDescriptor,
1444 List elements,
1445 List attributes)
1446 throws
1447 IntrospectionException {
1448 NodeDescriptor nodeDescriptor = XMLIntrospectorHelper
1449 .createDescriptor(propertyDescriptor,
1450 isAttributesForPrimitives(),
1451 this);
1452 if (nodeDescriptor == null) {
1453 return;
1454 }
1455 if (nodeDescriptor instanceof ElementDescriptor) {
1456 elements.add(nodeDescriptor);
1457 } else {
1458 attributes.add(nodeDescriptor);
1459 }
1460 }
1461
1462
1463 /**
1464 * Factory method to create XMLBeanInfo instances
1465 *
1466 * @param beanInfo the BeanInfo from which the XMLBeanInfo will be created
1467 * @return XMLBeanInfo describing the bean-xml mapping
1468 */
1469 protected XMLBeanInfo createXMLBeanInfo( BeanInfo beanInfo ) {
1470 XMLBeanInfo xmlBeanInfo = new XMLBeanInfo( beanInfo.getBeanDescriptor().getBeanClass() );
1471 return xmlBeanInfo;
1472 }
1473
1474 /**
1475 * Is this class a loop?
1476 *
1477 * @param type the Class to test
1478 * @return true if the type is a loop type
1479 */
1480 public boolean isLoopType(Class type) {
1481 return getConfiguration().isLoopType(type);
1482 }
1483
1484
1485 /**
1486 * Is this class a primitive?
1487 *
1488 * @param type the Class to test
1489 * @return true for primitive types
1490 */
1491 public boolean isPrimitiveType(Class type) {
1492 // TODO: this method will probably be deprecated when primitive types
1493 // are subsumed into the simple type concept
1494 TypeBindingStrategy.BindingType bindingType
1495 = configuration.getTypeBindingStrategy().bindingType( type ) ;
1496 boolean result = (bindingType.equals(TypeBindingStrategy.BindingType.PRIMITIVE));
1497 return result;
1498 }
1499
1500
1501 /** Some type of pseudo-bean */
1502 private abstract class BeanType {
1503 /**
1504 * Gets the name for this bean type
1505 * @return the bean type name, not null
1506 */
1507 public abstract String getBeanName();
1508
1509 /**
1510 * Gets the type to be used by the associated element
1511 * @return a Class that is the type not null
1512 */
1513 public abstract Class getElementType();
1514
1515 /**
1516 * Is this type a primitive?
1517 * @return true if this type should be treated by betwixt as a primitive
1518 */
1519 public abstract boolean isPrimitiveType();
1520
1521 /**
1522 * is this type a map?
1523 * @return true this should be treated as a map.
1524 */
1525 public abstract boolean isMapType();
1526
1527 /**
1528 * Is this type a loop?
1529 * @return true if this should be treated as a loop
1530 */
1531 public abstract boolean isLoopType();
1532
1533 /**
1534 * Gets the properties associated with this bean.
1535 * @return the BeanProperty's, not null
1536 */
1537 public abstract BeanProperty[] getProperties();
1538
1539 /**
1540 * Create string representation
1541 * @return something useful for logging
1542 */
1543 public String toString() {
1544 return "Bean[name=" + getBeanName() + ", type=" + getElementType();
1545 }
1546 }
1547
1548 /** Supports standard Java Beans */
1549 private class JavaBeanType extends BeanType {
1550 /** Introspected bean */
1551 private BeanInfo beanInfo;
1552 /** Bean class */
1553 private Class beanClass;
1554 /** Bean name */
1555 private String name;
1556 /** Bean properties */
1557 private BeanProperty[] properties;
1558
1559 /**
1560 * Constructs a BeanType for a standard Java Bean
1561 * @param beanInfo the BeanInfo describing the standard Java Bean, not null
1562 */
1563 public JavaBeanType(BeanInfo beanInfo) {
1564 this.beanInfo = beanInfo;
1565 BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
1566 beanClass = beanDescriptor.getBeanClass();
1567 name = beanDescriptor.getName();
1568 // Array's contain a bad character
1569 if (beanClass.isArray()) {
1570 // called all array's Array
1571 name = "Array";
1572 }
1573
1574 }
1575
1576 /** @see BeanType #getElementType */
1577 public Class getElementType() {
1578 return beanClass;
1579 }
1580
1581 /** @see BeanType#getBeanName */
1582 public String getBeanName() {
1583 return name;
1584 }
1585
1586 /** @see BeanType#isPrimitiveType */
1587 public boolean isPrimitiveType() {
1588 return XMLIntrospector.this.isPrimitiveType( beanClass );
1589 }
1590
1591 /** @see BeanType#isLoopType */
1592 public boolean isLoopType() {
1593 return getConfiguration().isLoopType( beanClass );
1594 }
1595
1596 /** @see BeanType#isMapType */
1597 public boolean isMapType() {
1598 return Map.class.isAssignableFrom( beanClass );
1599 }
1600
1601 /** @see BeanType#getProperties */
1602 public BeanProperty[] getProperties() {
1603 // lazy creation
1604 if ( properties == null ) {
1605 ArrayList propertyDescriptors = new ArrayList();
1606 // add base bean info
1607 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
1608 if ( descriptors != null ) {
1609 for (int i=0, size=descriptors.length; i<size; i++) {
1610 if (!getConfiguration().getPropertySuppressionStrategy()
1611 .suppressProperty(
1612 beanClass,
1613 descriptors[i].getPropertyType(),
1614 descriptors[i].getName())) {
1615 propertyDescriptors.add( descriptors[i] );
1616 }
1617 }
1618 }
1619
1620 // add properties from additional bean infos
1621 BeanInfo[] additionals = beanInfo.getAdditionalBeanInfo();
1622 if ( additionals != null ) {
1623 for ( int i=0, outerSize=additionals.length; i<outerSize; i++ ) {
1624 BeanInfo additionalInfo = additionals[i];
1625 descriptors = additionalInfo.getPropertyDescriptors();
1626 if ( descriptors != null ) {
1627 for (int j=0, innerSize=descriptors.length; j<innerSize; j++) {
1628 if (!getConfiguration().getPropertySuppressionStrategy()
1629 .suppressProperty(
1630 beanClass,
1631 descriptors[j].getPropertyType(),
1632 descriptors[j].getName())) {
1633 propertyDescriptors.add( descriptors[j] );
1634 }
1635 }
1636 }
1637 }
1638 }
1639
1640 addAllSuperinterfaces(beanClass, propertyDescriptors);
1641
1642 // what happens when size is zero?
1643 properties = new BeanProperty[ propertyDescriptors.size() ];
1644 int count = 0;
1645 for ( Iterator it = propertyDescriptors.iterator(); it.hasNext(); count++) {
1646 PropertyDescriptor propertyDescriptor = (PropertyDescriptor) it.next();
1647 properties[count] = new BeanProperty( propertyDescriptor );
1648 }
1649 }
1650 return properties;
1651 }
1652
1653 /**
1654 * Adds all super interfaces.
1655 * Super interface methods are not returned within the usual
1656 * bean info for an interface.
1657 * @param clazz <code>Class</code>, not null
1658 * @param propertyDescriptors <code>ArrayList</code> of <code>PropertyDescriptor</code>s', not null
1659 */
1660 private void addAllSuperinterfaces(Class clazz, ArrayList propertyDescriptors) {
1661 if (clazz.isInterface()) {
1662 Class[] superinterfaces = clazz.getInterfaces();
1663 for (int i=0, size=superinterfaces.length; i<size; i++) {
1664 try {
1665
1666 BeanInfo beanInfo;
1667 if( getConfiguration().ignoreAllBeanInfo() ) {
1668 beanInfo = Introspector.getBeanInfo( superinterfaces[i], Introspector.IGNORE_ALL_BEANINFO );
1669 }
1670 else {
1671 beanInfo = Introspector.getBeanInfo( superinterfaces[i] );
1672 }
1673 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
1674 for (int j=0, descriptorLength=descriptors.length; j<descriptorLength ; j++) {
1675 if (!getConfiguration().getPropertySuppressionStrategy()
1676 .suppressProperty(
1677 beanClass,
1678 descriptors[j].getPropertyType(),
1679 descriptors[j].getName())) {
1680 propertyDescriptors.add( descriptors[j] );
1681 }
1682 }
1683 addAllSuperinterfaces(superinterfaces[i], propertyDescriptors);
1684
1685 } catch (IntrospectionException ex) {
1686 log.info("Introspection on superinterface failed.", ex);
1687 }
1688 }
1689 }
1690 }
1691
1692 }
1693
1694 /** Implementation for DynaClasses */
1695 private class DynaClassBeanType extends BeanType {
1696 /** BeanType for this DynaClass */
1697 private DynaClass dynaClass;
1698 /** Properties extracted in constuctor */
1699 private BeanProperty[] properties;
1700
1701 /**
1702 * Constructs a BeanType for a DynaClass
1703 * @param dynaClass not null
1704 */
1705 public DynaClassBeanType(DynaClass dynaClass) {
1706 this.dynaClass = dynaClass;
1707 DynaProperty[] dynaProperties = dynaClass.getDynaProperties();
1708 properties = new BeanProperty[dynaProperties.length];
1709 for (int i=0, size=dynaProperties.length; i<size; i++) {
1710 properties[i] = new BeanProperty(dynaProperties[i]);
1711 }
1712 }
1713
1714 /** @see BeanType#getBeanName */
1715 public String getBeanName() {
1716 return dynaClass.getName();
1717 }
1718 /** @see BeanType#getElementType */
1719 public Class getElementType() {
1720 return DynaClass.class;
1721 }
1722 /** @see BeanType#isPrimitiveType */
1723 public boolean isPrimitiveType() {
1724 return false;
1725 }
1726 /** @see BeanType#isMapType */
1727 public boolean isMapType() {
1728 return false;
1729 }
1730 /** @see BeanType#isLoopType */
1731 public boolean isLoopType() {
1732 return false;
1733 }
1734 /** @see BeanType#getProperties */
1735 public BeanProperty[] getProperties() {
1736 return properties;
1737 }
1738 }
1739 }