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