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.Introspector;
60 import java.beans.IntrospectionException;
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.logging.LogFactory;
71 import org.apache.commons.logging.Log;
72
73 import org.apache.commons.betwixt.AttributeDescriptor;
74 import org.apache.commons.betwixt.ElementDescriptor;
75 import org.apache.commons.betwixt.NodeDescriptor;
76 import org.apache.commons.betwixt.XMLIntrospector;
77
78 import org.apache.commons.betwixt.expression.IteratorExpression;
79 import org.apache.commons.betwixt.expression.MethodExpression;
80 import org.apache.commons.betwixt.expression.MethodUpdater;
81
82 import org.apache.commons.betwixt.strategy.PluralStemmer;
83
84 /***
85 * <p><code>XMLIntrospectorHelper</code> a helper class for
86 * common code shared between the digestor and introspector.</p>
87 *
88 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
89 * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
90 * @version $Id: XMLIntrospectorHelper.java,v 1.6 2002/08/14 20:26:22 rdonkin Exp $
91 */
92 public class XMLIntrospectorHelper {
93
94 /*** Log used for logging (Doh!) */
95 protected static Log log = LogFactory.getLog( XMLIntrospectorHelper.class );
96
97 /*** Base constructor */
98 public XMLIntrospectorHelper() {
99 }
100
101 /***
102 * <p> Get the current logging implementation. </p>
103 */
104 public static Log getLog() {
105 return log;
106 }
107
108 /***
109 * <p> Set the current logging implementation. </p>
110 */
111 public static void setLog(Log aLog) {
112 log = aLog;
113 }
114
115
116 /***
117 * Process a property.
118 * Go through and work out whether it's a loop property, a primitive or a standard.
119 * The class property is ignored.
120 */
121 public static NodeDescriptor createDescriptor(
122 PropertyDescriptor propertyDescriptor,
123 boolean useAttributesForPrimitives,
124 XMLIntrospector introspector
125 ) throws IntrospectionException {
126 String name = propertyDescriptor.getName();
127 Class type = propertyDescriptor.getPropertyType();
128
129 if (log.isTraceEnabled()) {
130 log.trace("Creating descriptor for property: name="
131 + name + " type=" + type);
132 }
133
134 NodeDescriptor nodeDescriptor = null;
135 Method readMethod = propertyDescriptor.getReadMethod();
136 Method writeMethod = propertyDescriptor.getWriteMethod();
137
138 if ( readMethod == null ) {
139 if (log.isTraceEnabled()) {
140 log.trace( "No read method for property: name="
141 + name + " type=" + type);
142 }
143 return null;
144 }
145
146 if ( log.isTraceEnabled() ) {
147 log.trace( "Read method=" + readMethod.getName() );
148 }
149
150 // choose response from property type
151
152 // XXX: ignore class property ??
153 if ( Class.class.equals( type ) && "class".equals( name ) ) {
154 log.trace( "Ignoring class property" );
155 return null;
156 }
157 if ( isPrimitiveType( type ) ) {
158 if (log.isTraceEnabled()) {
159 log.trace( "Primitive type: " + name);
160 }
161 if ( useAttributesForPrimitives ) {
162 if (log.isTraceEnabled()) {
163 log.trace( "Adding property as attribute: " + name );
164 }
165 nodeDescriptor = new AttributeDescriptor();
166 }
167 else {
168 if (log.isTraceEnabled()) {
169 log.trace( "Adding property as element: " + name );
170 }
171 nodeDescriptor = new ElementDescriptor(true);
172 }
173 nodeDescriptor.setTextExpression( new MethodExpression( readMethod ) );
174
175 if ( writeMethod != null ) {
176 nodeDescriptor.setUpdater( new MethodUpdater( writeMethod ) );
177 }
178 }
179 else if ( isLoopType( type ) ) {
180 if (log.isTraceEnabled()) {
181 log.trace("Loop type: " + name);
182 }
183 ElementDescriptor loopDescriptor = new ElementDescriptor();
184 loopDescriptor.setContextExpression(
185 new IteratorExpression( new MethodExpression( readMethod ) )
186 );
187 // XXX: need to support some kind of 'add' or handle arrays, Lists or indexed properties
188 //loopDescriptor.setUpdater( new MethodUpdater( writeMethod ) );
189 if ( Map.class.isAssignableFrom( type ) ) {
190 loopDescriptor.setQualifiedName( "entry" );
191 }
192
193 ElementDescriptor elementDescriptor = new ElementDescriptor();
194 elementDescriptor.setWrapCollectionsInElement(introspector.isWrapCollectionsInElement());
195 elementDescriptor.setElementDescriptors( new ElementDescriptor[] { loopDescriptor } );
196
197 nodeDescriptor = elementDescriptor;
198 }
199 else {
200 if (log.isTraceEnabled()) {
201 log.trace( "Standard property: " + name);
202 }
203 ElementDescriptor elementDescriptor = new ElementDescriptor();
204 elementDescriptor.setContextExpression( new MethodExpression( readMethod ) );
205 if ( writeMethod != null ) {
206 elementDescriptor.setUpdater( new MethodUpdater( writeMethod ) );
207 }
208
209 nodeDescriptor = elementDescriptor;
210 }
211
212 nodeDescriptor.setLocalName( introspector.getNameMapper().mapTypeToElementName( name ) );
213 if (nodeDescriptor instanceof AttributeDescriptor) {
214 // we want to use the attributemapper only when it is an attribute..
215 nodeDescriptor.setLocalName( introspector.getAttributeNameMapper().mapTypeToElementName( name ) );
216 }
217 else {
218 nodeDescriptor.setLocalName( introspector.getElementNameMapper().mapTypeToElementName( name ) );
219 }
220
221 nodeDescriptor.setPropertyName( propertyDescriptor.getName() );
222 nodeDescriptor.setPropertyType( type );
223
224 // XXX: associate more bean information with the descriptor?
225 //nodeDescriptor.setDisplayName( propertyDescriptor.getDisplayName() );
226 //nodeDescriptor.setShortDescription( propertyDescriptor.getShortDescription() );
227 return nodeDescriptor;
228 }
229
230
231 public static void configureProperty( ElementDescriptor elementDescriptor, PropertyDescriptor propertyDescriptor ) {
232 Class type = propertyDescriptor.getPropertyType();
233 Method readMethod = propertyDescriptor.getReadMethod();
234 Method writeMethod = propertyDescriptor.getWriteMethod();
235
236 elementDescriptor.setLocalName( propertyDescriptor.getName() );
237 elementDescriptor.setPropertyType( type );
238
239 // XXX: associate more bean information with the descriptor?
240 //nodeDescriptor.setDisplayName( propertyDescriptor.getDisplayName() );
241 //nodeDescriptor.setShortDescription( propertyDescriptor.getShortDescription() );
242
243 if ( readMethod == null ) {
244 log.trace( "No read method" );
245 return;
246 }
247
248 if ( log.isTraceEnabled() ) {
249 log.trace( "Read method=" + readMethod.getName() );
250 }
251
252 // choose response from property type
253
254 // XXX: ignore class property ??
255 if ( Class.class.equals( type ) && "class".equals( propertyDescriptor.getName() ) ) {
256 log.trace( "Ignoring class property" );
257 return;
258 }
259 if ( isPrimitiveType( type ) ) {
260 elementDescriptor.setTextExpression( new MethodExpression( readMethod ) );
261 elementDescriptor.setPrimitiveType(true);
262 }
263 else if ( isLoopType( type ) ) {
264 log.trace("Loop type ??");
265
266 // don't wrap this in an extra element as its specified in the
267 // XML descriptor so no need.
268 elementDescriptor.setContextExpression(
269 new IteratorExpression( new MethodExpression( readMethod ) )
270 );
271
272 writeMethod = null;
273 }
274 else {
275 log.trace( "Standard property" );
276 elementDescriptor.setContextExpression( new MethodExpression( readMethod ) );
277 }
278
279 if ( writeMethod != null ) {
280 elementDescriptor.setUpdater( new MethodUpdater( writeMethod ) );
281 }
282 }
283
284
285 public static void configureProperty( AttributeDescriptor attributeDescriptor, PropertyDescriptor propertyDescriptor ) {
286 Class type = propertyDescriptor.getPropertyType();
287 Method readMethod = propertyDescriptor.getReadMethod();
288 Method writeMethod = propertyDescriptor.getWriteMethod();
289
290 if ( readMethod == null ) {
291 log.trace( "No read method" );
292 return;
293 }
294
295 if ( log.isTraceEnabled() ) {
296 log.trace( "Read method=" + readMethod );
297 }
298
299 // choose response from property type
300
301 // XXX: ignore class property ??
302 if ( Class.class.equals( type ) && "class".equals( propertyDescriptor.getName() ) ) {
303 log.trace( "Ignoring class property" );
304 return;
305 }
306 if ( isLoopType( type ) ) {
307 log.warn( "Using loop type for an attribute. Type = " + type.getName() + " attribute: " + attributeDescriptor.getQualifiedName() );
308 }
309
310 log.trace( "Standard property" );
311 attributeDescriptor.setTextExpression( new MethodExpression( readMethod ) );
312
313 if ( writeMethod != null ) {
314 attributeDescriptor.setUpdater( new MethodUpdater( writeMethod ) );
315 }
316
317 attributeDescriptor.setLocalName( propertyDescriptor.getName() );
318 attributeDescriptor.setPropertyType( type );
319
320 // XXX: associate more bean information with the descriptor?
321 //nodeDescriptor.setDisplayName( propertyDescriptor.getDisplayName() );
322 //nodeDescriptor.setShortDescription( propertyDescriptor.getShortDescription() );
323 }
324
325
326 /***
327 * Add any addPropety(PropertyType) methods as Updaters
328 * which are often used for 1-N relationships in beans.
329 * <br>
330 * The tricky part here is finding which ElementDescriptor corresponds
331 * to the method. e.g. a property 'items' might have an Element descriptor
332 * which the method addItem() should match to.
333 * <br>
334 * So the algorithm we'll use
335 * by default is to take the decapitalized name of the property being added
336 * and find the first ElementDescriptor that matches the property starting with
337 * the string. This should work for most use cases.
338 * e.g. addChild() would match the children property.
339 */
340 public static void defaultAddMethods( XMLIntrospector introspector, ElementDescriptor rootDescriptor, Class beanClass ) {
341 // lets iterate over all methods looking for one of the form
342 // add*(PropertyType)
343 if ( beanClass != null ) {
344 Method[] methods = beanClass.getMethods();
345 for ( int i = 0, size = methods.length; i < size; i++ ) {
346 Method method = methods[i];
347 String name = method.getName();
348 if ( name.startsWith( "add" ) ) {
349 // XXX: should we filter out non-void returning methods?
350 // some beans will return something as a helper
351 Class[] types = method.getParameterTypes();
352 if ( types != null && types.length == 1 ) {
353 String propertyName = Introspector.decapitalize( name.substring(3) );
354
355 // now lets try find the ElementDescriptor which displays
356 // a property which starts with propertyName
357 // and if so, we'll set a new Updater on it if there
358 // is not one already
359 ElementDescriptor descriptor = findGetCollectionDescriptor( introspector, rootDescriptor, propertyName );
360
361 if ( log.isDebugEnabled() ) {
362 log.debug( "!! " + propertyName + " -> " + descriptor);
363 }
364
365 if ( descriptor != null ) {
366 descriptor.setUpdater( new MethodUpdater( method ) );
367 descriptor.setSingularPropertyType( types[0] );
368
369 if ( log.isDebugEnabled() ) {
370 log.debug( "!! " + method);
371 log.debug( "!! " + types[0]);
372 }
373
374 // is there a child element with no localName
375 ElementDescriptor[] children = descriptor.getElementDescriptors();
376 if ( children != null && children.length > 0 ) {
377 ElementDescriptor child = children[0];
378 String localName = child.getLocalName();
379 if ( localName == null || localName.length() == 0 ) {
380 child.setLocalName( introspector.getElementNameMapper().mapTypeToElementName( propertyName ) );
381 }
382 }
383 }
384 else {
385 if ( log.isDebugEnabled() ) {
386 log.debug(
387 "Could not find an ElementDescriptor with property name: "
388 + propertyName + " to attach the add method: " + method
389 );
390 }
391 }
392 }
393 }
394 }
395 }
396 }
397
398 /*** Returns true if the type is a loop type */
399 public static boolean isLoopType(Class type) {
400 return type.isArray()
401 || Map.class.isAssignableFrom( type )
402 || Collection.class.isAssignableFrom( type )
403 || Enumeration.class.isAssignableFrom( type )
404 || Iterator.class.isAssignableFrom( type );
405 }
406
407
408 /*** Returns true for primitive types */
409 public static boolean isPrimitiveType(Class type) {
410 if ( type == null ) {
411 return false;
412 }
413 else if ( type.isPrimitive() ) {
414 return true;
415 }
416 else if ( type.equals( Object.class ) ) {
417 return false;
418 }
419 return type.getName().startsWith( "java.lang." )
420 || type.isAssignableFrom( Number.class )
421 || type.isAssignableFrom( String.class )
422 || type.isAssignableFrom( Date.class );
423 }
424
425 // Implementation methods
426 //-------------------------------------------------------------------------
427
428 /***
429 * Attempts to find the element descriptor for the getter property that
430 * typically matches a collection or array. The property name is used
431 * to match. e.g. if an addChild() method is detected the
432 * descriptor for the 'children' getter property should be returned.
433 */
434 protected static ElementDescriptor findGetCollectionDescriptor( XMLIntrospector introspector, ElementDescriptor rootDescriptor, String propertyName ) {
435 // create the Map of propertyName -> descriptor that the PluralStemmer will choose
436 Map map = new HashMap();
437 //String propertyName = rootDescriptor.getPropertyName();
438 if (propertyName != null) {
439 map.put(propertyName, rootDescriptor);
440 }
441 makeElementDescriptorMap( rootDescriptor, map );
442
443 PluralStemmer stemmer = introspector.getPluralStemmer();
444 ElementDescriptor elementDescriptor = stemmer.findPluralDescriptor( propertyName, map );
445
446 if ( log.isTraceEnabled() ) {
447 log.trace( "findPluralDescriptor( " + propertyName + " ):ElementDescriptor=" + elementDescriptor );
448 }
449
450 return elementDescriptor;
451 }
452
453 /***
454 * Creates a map where the keys are the property names and the values are the ElementDescriptors
455 */
456 protected static void makeElementDescriptorMap( ElementDescriptor rootDescriptor, Map map ) {
457 ElementDescriptor[] children = rootDescriptor.getElementDescriptors();
458 if ( children != null ) {
459 for ( int i = 0, size = children.length; i < size; i++ ) {
460 ElementDescriptor child = children[i];
461 String propertyName = child.getPropertyName();
462 if ( propertyName != null ) {
463 map.put( propertyName, child );
464 }
465 makeElementDescriptorMap( child, map );
466 }
467 }
468 }
469
470 /***
471 * Traverse the tree of element descriptors and find the oldValue and swap it with the newValue.
472 * This would be much easier to do if ElementDescriptor supported a parent relationship.
473 */
474 protected static void swapDescriptor( ElementDescriptor rootDescriptor, ElementDescriptor oldValue, ElementDescriptor newValue ) {
475 ElementDescriptor[] children = rootDescriptor.getElementDescriptors();
476 if ( children != null ) {
477 for ( int i = 0, size = children.length; i < size; i++ ) {
478 ElementDescriptor child = children[i];
479 if ( child == oldValue ) {
480 children[i] = newValue;
481 break;
482 }
483 swapDescriptor( child, oldValue, newValue );
484 }
485 }
486 }
487 }
This page was automatically generated by Maven