View Javadoc

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