1 package org.apache.commons.betwixt.digester;
2
3 /*
4 * ====================================================================
5 *
6 * The Apache Software License, Version 1.1
7 *
8 * Copyright (c) 1999-2002 The Apache Software Foundation. All rights
9 * reserved.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 *
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 *
18 * 2. Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in
20 * the documentation and/or other materials provided with the
21 * distribution.
22 *
23 * 3. The end-user documentation included with the redistribution, if
24 * any, must include the following acknowlegement:
25 * "This product includes software developed by the
26 * Apache Software Foundation (http://www.apache.org/)."
27 * Alternately, this acknowlegement may appear in the software itself,
28 * if and wherever such third-party acknowlegements normally appear.
29 *
30 * 4. The names "The Jakarta Project", "Commons", and "Apache Software
31 * Foundation" must not be used to endorse or promote products derived
32 * from this software without prior written permission. For written
33 * permission, please contact apache@apache.org.
34 *
35 * 5. Products derived from this software may not be called "Apache"
36 * nor may "Apache" appear in their names without prior written
37 * permission of the Apache Group.
38 *
39 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
40 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
41 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
42 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
43 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
44 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
45 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
46 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
47 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
48 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
49 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
50 * SUCH DAMAGE.
51 * ====================================================================
52 *
53 * This software consists of voluntary contributions made by many
54 * individuals on behalf of the Apache Software Foundation. For more
55 * information on the Apache Software Foundation, please see
56 * <http://www.apache.org/>.
57 */
58
59 import java.beans.IntrospectionException;
60 import java.beans.Introspector;
61 import java.beans.PropertyDescriptor;
62 import java.lang.reflect.Method;
63 import java.util.Collection;
64 import java.util.Date;
65 import java.util.Enumeration;
66 import java.util.HashMap;
67 import java.util.Iterator;
68 import java.util.Map;
69
70 import org.apache.commons.betwixt.AttributeDescriptor;
71 import org.apache.commons.betwixt.ElementDescriptor;
72 import org.apache.commons.betwixt.NodeDescriptor;
73 import org.apache.commons.betwixt.XMLIntrospector;
74 import org.apache.commons.betwixt.expression.IteratorExpression;
75 import org.apache.commons.betwixt.expression.MethodExpression;
76 import org.apache.commons.betwixt.expression.MethodUpdater;
77 import org.apache.commons.betwixt.strategy.PluralStemmer;
78 import org.apache.commons.logging.Log;
79 import org.apache.commons.logging.LogFactory;
80
81 /***
82 * <p><code>XMLIntrospectorHelper</code> a helper class for
83 * common code shared between the digestor and introspector.</p>
84 *
85 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
86 * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
87 * @version $Id: XMLIntrospectorHelper.java,v 1.16 2003/01/08 22:07:21 rdonkin Exp $
88 */
89 public class XMLIntrospectorHelper {
90
91 /*** Log used for logging (Doh!) */
92 protected static Log log = LogFactory.getLog( XMLIntrospectorHelper.class );
93
94 /*** Base constructor */
95 public XMLIntrospectorHelper() {
96 }
97
98 /***
99 * <p>Gets the current logging implementation.</p>
100 *
101 * @return current log
102 */
103 public static Log getLog() {
104 return log;
105 }
106
107 /***
108 * <p>Sets the current logging implementation.</p>
109 *
110 * @param aLog use this <code>Log</code>
111 */
112 public static void setLog(Log aLog) {
113 log = aLog;
114 }
115
116
117 /***
118 * Process a property.
119 * Go through and work out whether it's a loop property, a primitive or a standard.
120 * The class property is ignored.
121 *
122 * @param propertyDescriptor create a <code>NodeDescriptor</code> for this property
123 * @param useAttributesForPrimitives write primitives as attributes (rather than elements)
124 * @param introspector use this <code>XMLIntrospector</code>
125 * @return a correctly configured <code>NodeDescriptor</code> for the property
126 * @throws IntrospectionException when bean introspection fails
127 */
128 public static NodeDescriptor createDescriptor(
129 PropertyDescriptor propertyDescriptor,
130 boolean useAttributesForPrimitives,
131 XMLIntrospector introspector
132 ) throws IntrospectionException {
133 String name = propertyDescriptor.getName();
134 Class type = propertyDescriptor.getPropertyType();
135
136 if (log.isTraceEnabled()) {
137 log.trace("Creating descriptor for property: name="
138 + name + " type=" + type);
139 }
140
141 NodeDescriptor nodeDescriptor = null;
142 Method readMethod = propertyDescriptor.getReadMethod();
143 Method writeMethod = propertyDescriptor.getWriteMethod();
144
145 if ( readMethod == null ) {
146 if (log.isTraceEnabled()) {
147 log.trace( "No read method for property: name="
148 + name + " type=" + type);
149 }
150 return null;
151 }
152
153 if ( log.isTraceEnabled() ) {
154 log.trace( "Read method=" + readMethod.getName() );
155 }
156
157 // choose response from property type
158
159 // XXX: ignore class property ??
160 if ( Class.class.equals( type ) && "class".equals( name ) ) {
161 log.trace( "Ignoring class property" );
162 return null;
163 }
164 if ( isPrimitiveType( type ) ) {
165 if (log.isTraceEnabled()) {
166 log.trace( "Primitive type: " + name);
167 }
168 if ( useAttributesForPrimitives ) {
169 if (log.isTraceEnabled()) {
170 log.trace( "Adding property as attribute: " + name );
171 }
172 nodeDescriptor = new AttributeDescriptor();
173 } else {
174 if (log.isTraceEnabled()) {
175 log.trace( "Adding property as element: " + name );
176 }
177 nodeDescriptor = new ElementDescriptor(true);
178 }
179 nodeDescriptor.setTextExpression( new MethodExpression( readMethod ) );
180
181 if ( writeMethod != null ) {
182 nodeDescriptor.setUpdater( new MethodUpdater( writeMethod ) );
183 }
184 } else if ( isLoopType( type ) ) {
185 if (log.isTraceEnabled()) {
186 log.trace("Loop type: " + name);
187 }
188 ElementDescriptor loopDescriptor = new ElementDescriptor();
189 loopDescriptor.setContextExpression(
190 new IteratorExpression( new MethodExpression( readMethod ) )
191 );
192 // XXX: need to support some kind of 'add' or handle arrays, Lists or indexed properties
193 //loopDescriptor.setUpdater( new MethodUpdater( writeMethod ) );
194 if ( Map.class.isAssignableFrom( type ) ) {
195 loopDescriptor.setQualifiedName( "entry" );
196 }
197
198 ElementDescriptor elementDescriptor = new ElementDescriptor();
199 elementDescriptor.setWrapCollectionsInElement(
200 introspector.isWrapCollectionsInElement());
201 elementDescriptor.setElementDescriptors( new ElementDescriptor[] { loopDescriptor } );
202
203 nodeDescriptor = elementDescriptor;
204 } else {
205 if (log.isTraceEnabled()) {
206 log.trace( "Standard property: " + name);
207 }
208 ElementDescriptor elementDescriptor = new ElementDescriptor();
209 elementDescriptor.setContextExpression( new MethodExpression( readMethod ) );
210 if ( writeMethod != null ) {
211 elementDescriptor.setUpdater( new MethodUpdater( writeMethod ) );
212 }
213
214 nodeDescriptor = elementDescriptor;
215 }
216
217 if (nodeDescriptor instanceof AttributeDescriptor) {
218 // we want to use the attributemapper only when it is an attribute..
219 nodeDescriptor.setLocalName(
220 introspector.getAttributeNameMapper().mapTypeToElementName( name ) );
221 } else {
222 nodeDescriptor.setLocalName(
223 introspector.getElementNameMapper().mapTypeToElementName( name ) );
224 }
225
226 nodeDescriptor.setPropertyName( propertyDescriptor.getName() );
227 nodeDescriptor.setPropertyType( type );
228
229 // XXX: associate more bean information with the descriptor?
230 //nodeDescriptor.setDisplayName( propertyDescriptor.getDisplayName() );
231 //nodeDescriptor.setShortDescription( propertyDescriptor.getShortDescription() );
232 return nodeDescriptor;
233 }
234
235 /***
236 * Configure an <code>ElementDescriptor</code> from a <code>PropertyDescriptor</code>
237 *
238 * @param elementDescriptor configure this <code>ElementDescriptor</code>
239 * @param propertyDescriptor configure from this <code>PropertyDescriptor</code>
240 */
241 public static void configureProperty(
242 ElementDescriptor elementDescriptor,
243 PropertyDescriptor propertyDescriptor ) {
244 Class type = propertyDescriptor.getPropertyType();
245 Method readMethod = propertyDescriptor.getReadMethod();
246 Method writeMethod = propertyDescriptor.getWriteMethod();
247
248 elementDescriptor.setLocalName( propertyDescriptor.getName() );
249 elementDescriptor.setPropertyType( type );
250
251 // XXX: associate more bean information with the descriptor?
252 //nodeDescriptor.setDisplayName( propertyDescriptor.getDisplayName() );
253 //nodeDescriptor.setShortDescription( propertyDescriptor.getShortDescription() );
254
255 if ( readMethod == null ) {
256 log.trace( "No read method" );
257 return;
258 }
259
260 if ( log.isTraceEnabled() ) {
261 log.trace( "Read method=" + readMethod.getName() );
262 }
263
264 // choose response from property type
265
266 // XXX: ignore class property ??
267 if ( Class.class.equals( type ) && "class".equals( propertyDescriptor.getName() ) ) {
268 log.trace( "Ignoring class property" );
269 return;
270 }
271 if ( isPrimitiveType( type ) ) {
272 elementDescriptor.setTextExpression( new MethodExpression( readMethod ) );
273 elementDescriptor.setPrimitiveType(true);
274 } else if ( isLoopType( type ) ) {
275 log.trace("Loop type ??");
276
277 // don't wrap this in an extra element as its specified in the
278 // XML descriptor so no need.
279 elementDescriptor.setContextExpression(
280 new IteratorExpression( new MethodExpression( readMethod ) )
281 );
282
283 writeMethod = null;
284 } else {
285 log.trace( "Standard property" );
286 elementDescriptor.setContextExpression( new MethodExpression( readMethod ) );
287 }
288
289 if ( writeMethod != null ) {
290 elementDescriptor.setUpdater( new MethodUpdater( writeMethod ) );
291 }
292 }
293
294 /***
295 * Configure an <code>AttributeDescriptor</code> from a <code>PropertyDescriptor</code>
296 *
297 * @param attributeDescriptor configure this <code>AttributeDescriptor</code>
298 * @param propertyDescriptor configure from this <code>PropertyDescriptor</code>
299 */
300 public static void configureProperty(
301 AttributeDescriptor attributeDescriptor,
302 PropertyDescriptor propertyDescriptor ) {
303 Class type = propertyDescriptor.getPropertyType();
304 Method readMethod = propertyDescriptor.getReadMethod();
305 Method writeMethod = propertyDescriptor.getWriteMethod();
306
307 if ( readMethod == null ) {
308 log.trace( "No read method" );
309 return;
310 }
311
312 if ( log.isTraceEnabled() ) {
313 log.trace( "Read method=" + readMethod );
314 }
315
316 // choose response from property type
317
318 // XXX: ignore class property ??
319 if ( Class.class.equals( type ) && "class".equals( propertyDescriptor.getName() ) ) {
320 log.trace( "Ignoring class property" );
321 return;
322 }
323 if ( isLoopType( type ) ) {
324 log.warn( "Using loop type for an attribute. Type = "
325 + type.getName() + " attribute: " + attributeDescriptor.getQualifiedName() );
326 }
327
328 log.trace( "Standard property" );
329 attributeDescriptor.setTextExpression( new MethodExpression( readMethod ) );
330
331 if ( writeMethod != null ) {
332 attributeDescriptor.setUpdater( new MethodUpdater( writeMethod ) );
333 }
334
335 attributeDescriptor.setLocalName( propertyDescriptor.getName() );
336 attributeDescriptor.setPropertyType( type );
337
338 // XXX: associate more bean information with the descriptor?
339 //nodeDescriptor.setDisplayName( propertyDescriptor.getDisplayName() );
340 //nodeDescriptor.setShortDescription( propertyDescriptor.getShortDescription() );
341 }
342
343
344 /***
345 * Add any addPropety(PropertyType) methods as Updaters
346 * which are often used for 1-N relationships in beans.
347 * <br>
348 * The tricky part here is finding which ElementDescriptor corresponds
349 * to the method. e.g. a property 'items' might have an Element descriptor
350 * which the method addItem() should match to.
351 * <br>
352 * So the algorithm we'll use
353 * by default is to take the decapitalized name of the property being added
354 * and find the first ElementDescriptor that matches the property starting with
355 * the string. This should work for most use cases.
356 * e.g. addChild() would match the children property.
357 *
358 * @param introspector use this <code>XMLIntrospector</code> for introspection
359 * @param rootDescriptor add defaults to this descriptor
360 * @param beanClass the <code>Class</code> to which descriptor corresponds
361 */
362 public static void defaultAddMethods(
363 XMLIntrospector introspector,
364 ElementDescriptor rootDescriptor,
365 Class beanClass ) {
366 // lets iterate over all methods looking for one of the form
367 // add*(PropertyType)
368 if ( beanClass != null ) {
369 Method[] methods = beanClass.getMethods();
370 for ( int i = 0, size = methods.length; i < size; i++ ) {
371 Method method = methods[i];
372 String name = method.getName();
373 if ( name.startsWith( "add" ) ) {
374 // XXX: should we filter out non-void returning methods?
375 // some beans will return something as a helper
376 Class[] types = method.getParameterTypes();
377 if ( types != null && types.length == 1 ) {
378 String propertyName = Introspector.decapitalize( name.substring(3) );
379 if ( log.isTraceEnabled() ) {
380 log.trace( name + "->" + propertyName );
381 }
382
383 // now lets try find the ElementDescriptor which displays
384 // a property which starts with propertyName
385 // and if so, we'll set a new Updater on it if there
386 // is not one already
387 ElementDescriptor descriptor =
388 findGetCollectionDescriptor(
389 introspector,
390 rootDescriptor,
391 propertyName );
392
393 if ( log.isDebugEnabled() ) {
394 log.debug( "!! " + propertyName + " -> " + descriptor );
395 log.debug( "!! " + name + " -> " + descriptor.getPropertyName() );
396 }
397
398 if ( descriptor != null ) {
399 descriptor.setUpdater( new MethodUpdater( method ) );
400 descriptor.setSingularPropertyType( types[0] );
401
402 if ( log.isDebugEnabled() ) {
403 log.debug( "!! " + method);
404 log.debug( "!! " + types[0]);
405 }
406
407 // is there a child element with no localName
408 ElementDescriptor[] children = descriptor.getElementDescriptors();
409 if ( children != null && children.length > 0 ) {
410 ElementDescriptor child = children[0];
411 String localName = child.getLocalName();
412 if ( localName == null || localName.length() == 0 ) {
413 child.setLocalName(
414 introspector.getElementNameMapper()
415 .mapTypeToElementName( propertyName ) );
416 }
417 }
418 } else {
419 if ( log.isDebugEnabled() ) {
420 log.debug(
421 "Could not find an ElementDescriptor with property name: "
422 + propertyName + " to attach the add method: " + method
423 );
424 }
425 }
426 }
427 }
428 }
429 }
430 }
431
432 /***
433 * Is this a loop type class?
434 *
435 * @param type is this <code>Class</code> a loop type?
436 * @return true if the type is a loop type
437 */
438 public static boolean isLoopType(Class type) {
439 return type.isArray()
440 || Map.class.isAssignableFrom( type )
441 || Collection.class.isAssignableFrom( type )
442 || Enumeration.class.isAssignableFrom( type )
443 || Iterator.class.isAssignableFrom( type );
444 }
445
446
447 /***
448 * Is this a primitive type?
449 *
450 * @param type is this <code>Class<code> a primitive type?
451 * @return true for primitive types
452 */
453 public static boolean isPrimitiveType(Class type) {
454 if ( type == null ) {
455 return false;
456
457 } else if ( type.isPrimitive() ) {
458 return true;
459
460 } else if ( type.equals( Object.class ) ) {
461 return false;
462 }
463 return type.getName().startsWith( "java.lang." )
464 || type.isAssignableFrom( Number.class )
465 || type.isAssignableFrom( String.class )
466 || type.isAssignableFrom( Date.class )
467 || type.isAssignableFrom( java.sql.Date.class )
468 || type.isAssignableFrom( java.sql.Time.class )
469 || type.isAssignableFrom( java.sql.Timestamp.class )
470 || type.isAssignableFrom( java.math.BigDecimal.class )
471 || type.isAssignableFrom( java.math.BigInteger.class );
472 }
473
474 // Implementation methods
475 //-------------------------------------------------------------------------
476
477 /***
478 * Attempts to find the element descriptor for the getter property that
479 * typically matches a collection or array. The property name is used
480 * to match. e.g. if an addChild() method is detected the
481 * descriptor for the 'children' getter property should be returned.
482 *
483 * @param introspector use this <code>XMLIntrospector</code>
484 * @param rootDescriptor the <code>ElementDescriptor</code> whose child element will be
485 * searched for a match
486 * @param propertyName the name of the 'adder' method to match
487 * @return <code>ElementDescriptor</code> for the matching getter
488 */
489 protected static ElementDescriptor findGetCollectionDescriptor(
490 XMLIntrospector introspector,
491 ElementDescriptor rootDescriptor,
492 String propertyName ) {
493 // create the Map of propertyName -> descriptor that the PluralStemmer will choose
494 Map map = new HashMap();
495 //String propertyName = rootDescriptor.getPropertyName();
496 if ( log.isTraceEnabled() ) {
497 log.trace( "findPluralDescriptor( " + propertyName
498 + " ):root property name=" + rootDescriptor.getPropertyName() );
499 }
500
501 if (rootDescriptor.getPropertyName() != null) {
502 map.put(propertyName, rootDescriptor);
503 }
504 makeElementDescriptorMap( rootDescriptor, map );
505
506 PluralStemmer stemmer = introspector.getPluralStemmer();
507 ElementDescriptor elementDescriptor = stemmer.findPluralDescriptor( propertyName, map );
508
509 if ( log.isTraceEnabled() ) {
510 log.trace(
511 "findPluralDescriptor( " + propertyName
512 + " ):ElementDescriptor=" + elementDescriptor );
513 }
514
515 return elementDescriptor;
516 }
517
518 /***
519 * Creates a map where the keys are the property names and the values are the ElementDescriptors
520 *
521 * @param rootDescriptor the values of the maps are the children of this
522 * <code>ElementDescriptor</code> index by their property names
523 * @param map the map to which the elements will be added
524 */
525 protected static void makeElementDescriptorMap( ElementDescriptor rootDescriptor, Map map ) {
526 ElementDescriptor[] children = rootDescriptor.getElementDescriptors();
527 if ( children != null ) {
528 for ( int i = 0, size = children.length; i < size; i++ ) {
529 ElementDescriptor child = children[i];
530 String propertyName = child.getPropertyName();
531 if ( propertyName != null ) {
532 map.put( propertyName, child );
533 }
534 makeElementDescriptorMap( child, map );
535 }
536 }
537 }
538
539 /***
540 * Traverse the tree of element descriptors and find the oldValue and swap it with the newValue.
541 * This would be much easier to do if ElementDescriptor supported a parent relationship.
542 *
543 * @param rootDescriptor traverse child graph for this <code>ElementDescriptor</code>
544 * @param oldValue replace this <code>ElementDescriptor</code>
545 * @param newValue replace with this <code>ElementDescriptor</code>
546 */
547 protected static void swapDescriptor(
548 ElementDescriptor rootDescriptor,
549 ElementDescriptor oldValue,
550 ElementDescriptor newValue ) {
551 ElementDescriptor[] children = rootDescriptor.getElementDescriptors();
552 if ( children != null ) {
553 for ( int i = 0, size = children.length; i < size; i++ ) {
554 ElementDescriptor child = children[i];
555 if ( child == oldValue ) {
556 children[i] = newValue;
557 break;
558 }
559 swapDescriptor( child, oldValue, newValue );
560 }
561 }
562 }
563 }
This page was automatically generated by Maven