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