1 package org.apache.commons.betwixt.digester;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 import java.beans.IntrospectionException;
21 import java.beans.Introspector;
22 import java.beans.PropertyDescriptor;
23 import java.lang.reflect.Method;
24 import java.util.Collection;
25 import java.util.Date;
26 import java.util.Enumeration;
27 import java.util.HashMap;
28 import java.util.Iterator;
29 import java.util.Map;
30
31 import org.apache.commons.betwixt.AttributeDescriptor;
32 import org.apache.commons.betwixt.ElementDescriptor;
33 import org.apache.commons.betwixt.NodeDescriptor;
34 import org.apache.commons.betwixt.XMLIntrospector;
35 import org.apache.commons.betwixt.expression.IteratorExpression;
36 import org.apache.commons.betwixt.expression.MapEntryAdder;
37 import org.apache.commons.betwixt.expression.MethodExpression;
38 import org.apache.commons.betwixt.expression.MethodUpdater;
39 import org.apache.commons.betwixt.strategy.PluralStemmer;
40 import org.apache.commons.logging.Log;
41 import org.apache.commons.logging.LogFactory;
42
43 /***
44 * <p><code>XMLIntrospectorHelper</code> a helper class for
45 * common code shared between the digestor and introspector.</p>
46 *
47 * TODO this class will be deprecated soon
48 * need to move the isLoop and isPrimitiveType but probably need to
49 * think about whether they need replacing with something different.
50 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
51 * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
52 *
53 * @deprecated
54 */
55 public class XMLIntrospectorHelper {
56
57 /*** Log used for logging (Doh!) */
58 protected static Log log = LogFactory.getLog( XMLIntrospectorHelper.class );
59
60 /*** Base constructor */
61 public XMLIntrospectorHelper() {
62 }
63
64 /***
65 * <p>Gets the current logging implementation.</p>
66 *
67 * @return current log
68 */
69 public static Log getLog() {
70 return log;
71 }
72
73 /***
74 * <p>Sets the current logging implementation.</p>
75 *
76 * @param aLog use this <code>Log</code>
77 */
78 public static void setLog(Log aLog) {
79 log = aLog;
80 }
81
82
83
84 /***
85 * Process a property.
86 * Go through and work out whether it's a loop property, a primitive or a standard.
87 * The class property is ignored.
88 *
89 * @param propertyDescriptor create a <code>NodeDescriptor</code> for this property
90 * @param useAttributesForPrimitives write primitives as attributes (rather than elements)
91 * @param introspector use this <code>XMLIntrospector</code>
92 * @return a correctly configured <code>NodeDescriptor</code> for the property
93 * @throws IntrospectionException when bean introspection fails
94 * @deprecated 0.5 this method has been replaced by {@link XMLIntrospector#createDescriptor}
95 */
96 public static NodeDescriptor createDescriptor(
97 PropertyDescriptor propertyDescriptor,
98 boolean useAttributesForPrimitives,
99 XMLIntrospector introspector
100 ) throws IntrospectionException {
101 String name = propertyDescriptor.getName();
102 Class type = propertyDescriptor.getPropertyType();
103
104 if (log.isTraceEnabled()) {
105 log.trace("Creating descriptor for property: name="
106 + name + " type=" + type);
107 }
108
109 NodeDescriptor nodeDescriptor = null;
110 Method readMethod = propertyDescriptor.getReadMethod();
111 Method writeMethod = propertyDescriptor.getWriteMethod();
112
113 if ( readMethod == null ) {
114 if (log.isTraceEnabled()) {
115 log.trace( "No read method for property: name="
116 + name + " type=" + type);
117 }
118 return null;
119 }
120
121 if ( log.isTraceEnabled() ) {
122 log.trace( "Read method=" + readMethod.getName() );
123 }
124
125
126
127
128 if ( Class.class.equals( type ) && "class".equals( name ) ) {
129 log.trace( "Ignoring class property" );
130 return null;
131 }
132 if ( isPrimitiveType( type ) ) {
133 if (log.isTraceEnabled()) {
134 log.trace( "Primitive type: " + name);
135 }
136 if ( useAttributesForPrimitives ) {
137 if (log.isTraceEnabled()) {
138 log.trace( "Adding property as attribute: " + name );
139 }
140 nodeDescriptor = new AttributeDescriptor();
141 } else {
142 if (log.isTraceEnabled()) {
143 log.trace( "Adding property as element: " + name );
144 }
145 nodeDescriptor = new ElementDescriptor(true);
146 }
147 nodeDescriptor.setTextExpression( new MethodExpression( readMethod ) );
148
149 if ( writeMethod != null ) {
150 nodeDescriptor.setUpdater( new MethodUpdater( writeMethod ) );
151 }
152 } else if ( isLoopType( type ) ) {
153 if (log.isTraceEnabled()) {
154 log.trace("Loop type: " + name);
155 log.trace("Wrap in collections? " + introspector.isWrapCollectionsInElement());
156 }
157 ElementDescriptor loopDescriptor = new ElementDescriptor();
158 loopDescriptor.setContextExpression(
159 new IteratorExpression( new MethodExpression( readMethod ) )
160 );
161 loopDescriptor.setWrapCollectionsInElement(
162 introspector.isWrapCollectionsInElement());
163
164
165 if ( Map.class.isAssignableFrom( type ) ) {
166 loopDescriptor.setQualifiedName( "entry" );
167
168 loopDescriptor.addElementDescriptor( new ElementDescriptor( "key" ) );
169 loopDescriptor.addElementDescriptor( new ElementDescriptor( "value" ) );
170 }
171
172 ElementDescriptor elementDescriptor = new ElementDescriptor();
173 elementDescriptor.setWrapCollectionsInElement(
174 introspector.isWrapCollectionsInElement());
175 elementDescriptor.setElementDescriptors( new ElementDescriptor[] { loopDescriptor } );
176
177 nodeDescriptor = elementDescriptor;
178 } else {
179 if (log.isTraceEnabled()) {
180 log.trace( "Standard property: " + name);
181 }
182 ElementDescriptor elementDescriptor = new ElementDescriptor();
183 elementDescriptor.setContextExpression( new MethodExpression( readMethod ) );
184 if ( writeMethod != null ) {
185 elementDescriptor.setUpdater( new MethodUpdater( writeMethod ) );
186 }
187
188 nodeDescriptor = elementDescriptor;
189 }
190
191 if (nodeDescriptor instanceof AttributeDescriptor) {
192
193 nodeDescriptor.setLocalName(
194 introspector.getAttributeNameMapper().mapTypeToElementName( name ) );
195 } else {
196 nodeDescriptor.setLocalName(
197 introspector.getElementNameMapper().mapTypeToElementName( name ) );
198 }
199
200 nodeDescriptor.setPropertyName( propertyDescriptor.getName() );
201 nodeDescriptor.setPropertyType( type );
202
203
204
205
206
207 if (log.isTraceEnabled()) {
208 log.trace("Created descriptor:");
209 log.trace(nodeDescriptor);
210 }
211 return nodeDescriptor;
212 }
213
214 /***
215 * Configure an <code>ElementDescriptor</code> from a <code>PropertyDescriptor</code>.
216 * This uses default element updater (the write method of the property).
217 *
218 * @param elementDescriptor configure this <code>ElementDescriptor</code>
219 * @param propertyDescriptor configure from this <code>PropertyDescriptor</code>
220 * @deprecated 0.6 unused
221 */
222 public static void configureProperty(
223 ElementDescriptor elementDescriptor,
224 PropertyDescriptor propertyDescriptor ) {
225
226 configureProperty( elementDescriptor, propertyDescriptor, null, null);
227 }
228
229 /***
230 * Configure an <code>ElementDescriptor</code> from a <code>PropertyDescriptor</code>.
231 * A custom update method may be set.
232 *
233 * @param elementDescriptor configure this <code>ElementDescriptor</code>
234 * @param propertyDescriptor configure from this <code>PropertyDescriptor</code>
235 * @param updateMethodName the name of the custom updater method to user.
236 * If null, then then
237 * @param beanClass the <code>Class</code> from which the update method should be found.
238 * This may be null only when <code>updateMethodName</code> is also null.
239 * @since 0.5
240 * @deprecated 0.6 moved into ElementRule
241 */
242 public static void configureProperty(
243 ElementDescriptor elementDescriptor,
244 PropertyDescriptor propertyDescriptor,
245 String updateMethodName,
246 Class beanClass ) {
247
248 Class type = propertyDescriptor.getPropertyType();
249 Method readMethod = propertyDescriptor.getReadMethod();
250 Method writeMethod = propertyDescriptor.getWriteMethod();
251
252 elementDescriptor.setLocalName( propertyDescriptor.getName() );
253 elementDescriptor.setPropertyType( type );
254
255
256
257
258
259 if ( readMethod == null ) {
260 log.trace( "No read method" );
261 return;
262 }
263
264 if ( log.isTraceEnabled() ) {
265 log.trace( "Read method=" + readMethod.getName() );
266 }
267
268
269
270
271 if ( Class.class.equals( type ) && "class".equals( propertyDescriptor.getName() ) ) {
272 log.trace( "Ignoring class property" );
273 return;
274 }
275 if ( isPrimitiveType( type ) ) {
276 elementDescriptor.setTextExpression( new MethodExpression( readMethod ) );
277
278 } else if ( isLoopType( type ) ) {
279 log.trace("Loop type ??");
280
281
282
283 elementDescriptor.setContextExpression(
284 new IteratorExpression( new MethodExpression( readMethod ) )
285 );
286
287 writeMethod = null;
288 } else {
289 log.trace( "Standard property" );
290 elementDescriptor.setContextExpression( new MethodExpression( readMethod ) );
291 }
292
293
294 if (updateMethodName == null) {
295
296 if ( writeMethod != null ) {
297 elementDescriptor.setUpdater( new MethodUpdater( writeMethod ) );
298 }
299
300 } else {
301
302 if ( log.isTraceEnabled() ) {
303 log.trace( "Finding custom method: " );
304 log.trace( " on:" + beanClass );
305 log.trace( " name:" + updateMethodName );
306 }
307
308 Method updateMethod = null;
309 Method[] methods = beanClass.getMethods();
310 for ( int i = 0, size = methods.length; i < size; i++ ) {
311 Method method = methods[i];
312 if ( updateMethodName.equals( method.getName() ) ) {
313
314
315 if (methods[i].getParameterTypes().length == 1) {
316
317 updateMethod = methods[i];
318 if ( log.isTraceEnabled() ) {
319 log.trace("Matched method:" + updateMethod);
320 }
321
322 break;
323 }
324 }
325 }
326
327 if (updateMethod == null) {
328 if ( log.isInfoEnabled() ) {
329
330 log.info("No method with name '" + updateMethodName + "' found for update");
331 }
332 } else {
333
334 elementDescriptor.setUpdater( new MethodUpdater( updateMethod ) );
335 elementDescriptor.setSingularPropertyType( updateMethod.getParameterTypes()[0] );
336 if ( log.isTraceEnabled() ) {
337 log.trace( "Set custom updater on " + elementDescriptor);
338 }
339 }
340 }
341 }
342
343 /***
344 * Configure an <code>AttributeDescriptor</code> from a <code>PropertyDescriptor</code>
345 *
346 * @param attributeDescriptor configure this <code>AttributeDescriptor</code>
347 * @param propertyDescriptor configure from this <code>PropertyDescriptor</code>
348 * @deprecated 0.6 moved into AttributeRule
349 */
350 public static void configureProperty(
351 AttributeDescriptor attributeDescriptor,
352 PropertyDescriptor propertyDescriptor ) {
353 Class type = propertyDescriptor.getPropertyType();
354 Method readMethod = propertyDescriptor.getReadMethod();
355 Method writeMethod = propertyDescriptor.getWriteMethod();
356
357 if ( readMethod == null ) {
358 log.trace( "No read method" );
359 return;
360 }
361
362 if ( log.isTraceEnabled() ) {
363 log.trace( "Read method=" + readMethod );
364 }
365
366
367
368
369 if ( Class.class.equals( type ) && "class".equals( propertyDescriptor.getName() ) ) {
370 log.trace( "Ignoring class property" );
371 return;
372 }
373 if ( isLoopType( type ) ) {
374 log.warn( "Using loop type for an attribute. Type = "
375 + type.getName() + " attribute: " + attributeDescriptor.getQualifiedName() );
376 }
377
378 log.trace( "Standard property" );
379 attributeDescriptor.setTextExpression( new MethodExpression( readMethod ) );
380
381 if ( writeMethod != null ) {
382 attributeDescriptor.setUpdater( new MethodUpdater( writeMethod ) );
383 }
384
385 attributeDescriptor.setLocalName( propertyDescriptor.getName() );
386 attributeDescriptor.setPropertyType( type );
387
388
389
390
391 }
392
393
394 /***
395 * Add any addPropety(PropertyType) methods as Updaters
396 * which are often used for 1-N relationships in beans.
397 * <br>
398 * The tricky part here is finding which ElementDescriptor corresponds
399 * to the method. e.g. a property 'items' might have an Element descriptor
400 * which the method addItem() should match to.
401 * <br>
402 * So the algorithm we'll use
403 * by default is to take the decapitalized name of the property being added
404 * and find the first ElementDescriptor that matches the property starting with
405 * the string. This should work for most use cases.
406 * e.g. addChild() would match the children property.
407 *
408 * @param introspector use this <code>XMLIntrospector</code> for introspection
409 * @param rootDescriptor add defaults to this descriptor
410 * @param beanClass the <code>Class</code> to which descriptor corresponds
411 * @deprecated 0.6 use the method in XMLIntrospector instead
412 */
413 public static void defaultAddMethods(
414 XMLIntrospector introspector,
415 ElementDescriptor rootDescriptor,
416 Class beanClass ) {
417
418
419 if ( beanClass != null ) {
420 Method[] methods = beanClass.getMethods();
421 for ( int i = 0, size = methods.length; i < size; i++ ) {
422 Method method = methods[i];
423 String name = method.getName();
424 if ( name.startsWith( "add" ) ) {
425
426
427 Class[] types = method.getParameterTypes();
428 if ( types != null) {
429 if ( log.isTraceEnabled() ) {
430 log.trace("Searching for match for " + method);
431 }
432
433 if ( ( types.length == 1 ) || types.length == 2 ) {
434 String propertyName = Introspector.decapitalize( name.substring(3) );
435 if (propertyName.length() == 0)
436 continue;
437 if ( log.isTraceEnabled() ) {
438 log.trace( name + "->" + propertyName );
439 }
440
441
442
443
444
445 ElementDescriptor descriptor =
446 findGetCollectionDescriptor(
447 introspector,
448 rootDescriptor,
449 propertyName );
450
451 if ( log.isDebugEnabled() ) {
452 log.debug( "!! " + propertyName + " -> " + descriptor );
453 log.debug( "!! " + name + " -> "
454 + (descriptor!=null?descriptor.getPropertyName():"") );
455 }
456 if ( descriptor != null ) {
457 boolean isMapDescriptor
458 = Map.class.isAssignableFrom( descriptor.getPropertyType() );
459 if ( !isMapDescriptor && types.length == 1 ) {
460
461 log.trace("Matching collection or iteration");
462
463 descriptor.setUpdater( new MethodUpdater( method ) );
464 descriptor.setSingularPropertyType( types[0] );
465
466 if ( log.isDebugEnabled() ) {
467 log.debug( "!! " + method);
468 log.debug( "!! " + types[0]);
469 }
470
471
472 ElementDescriptor[] children
473 = descriptor.getElementDescriptors();
474 if ( children != null && children.length > 0 ) {
475 ElementDescriptor child = children[0];
476 String localName = child.getLocalName();
477 if ( localName == null || localName.length() == 0 ) {
478 child.setLocalName(
479 introspector.getElementNameMapper()
480 .mapTypeToElementName( propertyName ) );
481 }
482 }
483
484 } else if ( isMapDescriptor && types.length == 2 ) {
485
486 log.trace("Matching map");
487 ElementDescriptor[] children
488 = descriptor.getElementDescriptors();
489
490 if ( children.length == 0 ) {
491
492 log.info(
493 "'entry' descriptor is missing for map. "
494 + "Updaters cannot be set");
495
496 } else {
497
498
499 ElementDescriptor[] grandchildren
500 = children[0].getElementDescriptors();
501 MapEntryAdder adder = new MapEntryAdder(method);
502 for (
503 int n=0,
504 noOfGrandChildren = grandchildren.length;
505 n < noOfGrandChildren;
506 n++ ) {
507 if ( "key".equals(
508 grandchildren[n].getLocalName() ) ) {
509
510 grandchildren[n].setUpdater(
511 adder.getKeyUpdater() );
512 grandchildren[n].setSingularPropertyType(
513 types[0] );
514 if ( log.isTraceEnabled() ) {
515 log.trace(
516 "Key descriptor: " + grandchildren[n]);
517 }
518
519 } else if (
520 "value".equals(
521 grandchildren[n].getLocalName() ) ) {
522
523 grandchildren[n].setUpdater(
524 adder.getValueUpdater() );
525 grandchildren[n].setSingularPropertyType(
526 types[1] );
527 if ( log.isTraceEnabled() ) {
528 log.trace(
529 "Value descriptor: " + grandchildren[n]);
530 }
531 }
532 }
533 }
534 }
535 } else {
536 if ( log.isDebugEnabled() ) {
537 log.debug(
538 "Could not find an ElementDescriptor with property name: "
539 + propertyName + " to attach the add method: " + method
540 );
541 }
542 }
543 }
544 }
545 }
546 }
547 }
548 }
549
550 /***
551 * Is this a loop type class?
552 *
553 * @param type is this <code>Class</code> a loop type?
554 * @return true if the type is a loop type, or if type is null
555 * @deprecated 0.7 replaced by {@link org.apache.commons.betwixt.IntrospectionConfiguration#isLoopType(Class)}
556 */
557 public static boolean isLoopType(Class type) {
558
559 if (type == null) {
560 log.trace("isLoopType: type is null");
561 return false;
562 }
563 return type.isArray()
564 || Map.class.isAssignableFrom( type )
565 || Collection.class.isAssignableFrom( type )
566 || Enumeration.class.isAssignableFrom( type )
567 || Iterator.class.isAssignableFrom( type );
568 }
569
570
571 /***
572 * Is this a primitive type?
573 *
574 * TODO: this method will probably be removed when primitive types
575 * are subsumed into the simple type concept.
576 * This needs moving into XMLIntrospector so that the list of simple
577 * type can be varied.
578 * @param type is this <code>Class<code> a primitive type?
579 * @return true for primitive types
580 * @deprecated 0.6 replaced by {@link org.apache.commons.betwixt.strategy.TypeBindingStrategy}
581 */
582 public static boolean isPrimitiveType(Class type) {
583 if ( type == null ) {
584 return false;
585
586 } else if ( type.isPrimitive() ) {
587 return true;
588
589 } else if ( type.equals( Object.class ) ) {
590 return false;
591 }
592 return type.getName().startsWith( "java.lang." )
593 || Number.class.isAssignableFrom( type )
594 || String.class.isAssignableFrom( type )
595 || Date.class.isAssignableFrom( type )
596 || java.sql.Date.class.isAssignableFrom( type )
597 || java.sql.Time.class.isAssignableFrom( type )
598 || java.sql.Timestamp.class.isAssignableFrom( type )
599 || java.math.BigDecimal.class.isAssignableFrom( type )
600 || java.math.BigInteger.class.isAssignableFrom( type );
601 }
602
603
604
605
606 /***
607 * Attempts to find the element descriptor for the getter property that
608 * typically matches a collection or array. The property name is used
609 * to match. e.g. if an addChild() method is detected the
610 * descriptor for the 'children' getter property should be returned.
611 *
612 * @param introspector use this <code>XMLIntrospector</code>
613 * @param rootDescriptor the <code>ElementDescriptor</code> whose child element will be
614 * searched for a match
615 * @param propertyName the name of the 'adder' method to match
616 * @return <code>ElementDescriptor</code> for the matching getter
617 * @deprecated 0.6 moved into XMLIntrospector
618 */
619 protected static ElementDescriptor findGetCollectionDescriptor(
620 XMLIntrospector introspector,
621 ElementDescriptor rootDescriptor,
622 String propertyName ) {
623
624 Map map = new HashMap();
625
626 if ( log.isTraceEnabled() ) {
627 log.trace( "findPluralDescriptor( " + propertyName
628 + " ):root property name=" + rootDescriptor.getPropertyName() );
629 }
630
631 if (rootDescriptor.getPropertyName() != null) {
632 map.put(propertyName, rootDescriptor);
633 }
634 makeElementDescriptorMap( rootDescriptor, map );
635
636 PluralStemmer stemmer = introspector.getPluralStemmer();
637 ElementDescriptor elementDescriptor = stemmer.findPluralDescriptor( propertyName, map );
638
639 if ( log.isTraceEnabled() ) {
640 log.trace(
641 "findPluralDescriptor( " + propertyName
642 + " ):ElementDescriptor=" + elementDescriptor );
643 }
644
645 return elementDescriptor;
646 }
647
648 /***
649 * Creates a map where the keys are the property names and the values are the ElementDescriptors
650 *
651 * @param rootDescriptor the values of the maps are the children of this
652 * <code>ElementDescriptor</code> index by their property names
653 * @param map the map to which the elements will be added
654 * @deprecated 0.6 moved into XMLIntrospector
655 */
656 protected static void makeElementDescriptorMap( ElementDescriptor rootDescriptor, Map map ) {
657 ElementDescriptor[] children = rootDescriptor.getElementDescriptors();
658 if ( children != null ) {
659 for ( int i = 0, size = children.length; i < size; i++ ) {
660 ElementDescriptor child = children[i];
661 String propertyName = child.getPropertyName();
662 if ( propertyName != null ) {
663 map.put( propertyName, child );
664 }
665 makeElementDescriptorMap( child, map );
666 }
667 }
668 }
669
670 /***
671 * Traverse the tree of element descriptors and find the oldValue and swap it with the newValue.
672 * This would be much easier to do if ElementDescriptor supported a parent relationship.
673 *
674 * @param rootDescriptor traverse child graph for this <code>ElementDescriptor</code>
675 * @param oldValue replace this <code>ElementDescriptor</code>
676 * @param newValue replace with this <code>ElementDescriptor</code>
677 * @deprecated 0.6 now unused
678 */
679 protected static void swapDescriptor(
680 ElementDescriptor rootDescriptor,
681 ElementDescriptor oldValue,
682 ElementDescriptor newValue ) {
683 ElementDescriptor[] children = rootDescriptor.getElementDescriptors();
684 if ( children != null ) {
685 for ( int i = 0, size = children.length; i < size; i++ ) {
686 ElementDescriptor child = children[i];
687 if ( child == oldValue ) {
688 children[i] = newValue;
689 break;
690 }
691 swapDescriptor( child, oldValue, newValue );
692 }
693 }
694 }
695 }