Package org.apache.commons.betwixt

Source Code of org.apache.commons.betwixt.XMLIntrospector$DynaClassBeanType

package org.apache.commons.betwixt;

/*
* Copyright 2001-2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import java.beans.BeanDescriptor;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.DynaClass;
import org.apache.commons.beanutils.DynaProperty;
import org.apache.commons.betwixt.digester.XMLBeanInfoDigester;
import org.apache.commons.betwixt.digester.XMLIntrospectorHelper;
import org.apache.commons.betwixt.expression.EmptyExpression;
import org.apache.commons.betwixt.expression.IteratorExpression;
import org.apache.commons.betwixt.expression.MapEntryAdder;
import org.apache.commons.betwixt.expression.MethodUpdater;
import org.apache.commons.betwixt.expression.StringExpression;
import org.apache.commons.betwixt.registry.DefaultXMLBeanInfoRegistry;
import org.apache.commons.betwixt.registry.XMLBeanInfoRegistry;
import org.apache.commons.betwixt.strategy.ClassNormalizer;
import org.apache.commons.betwixt.strategy.DefaultNameMapper;
import org.apache.commons.betwixt.strategy.DefaultPluralStemmer;
import org.apache.commons.betwixt.strategy.NameMapper;
import org.apache.commons.betwixt.strategy.PluralStemmer;
import org.apache.commons.betwixt.strategy.TypeBindingStrategy;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
  * <p><code>XMLIntrospector</code> an introspector of beans to create a
  * XMLBeanInfo instance.</p>
  *
  * <p>By default, <code>XMLBeanInfo</code> caching is switched on.
  * This means that the first time that a request is made for a <code>XMLBeanInfo</code>
  * for a particular class, the <code>XMLBeanInfo</code> is cached.
  * Later requests for the same class will return the cached value.</p>
  *
  * <p>Note :</p>
  * <p>This class makes use of the <code>java.bean.Introspector</code>
  * class, which contains a BeanInfoSearchPath. To make sure betwixt can
  * do his work correctly, this searchpath is completely ignored during
  * processing. The original values will be restored after processing finished
  * </p>
  *
  * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
  * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
  */
public class XMLIntrospector {
    /**
     * Log used for logging (Doh!)
     * @deprecated 0.6 use the {@link #getLog()} property instead
     */   
    protected Log log = LogFactory.getLog( XMLIntrospector.class );
   
    /** Maps classes to <code>XMLBeanInfo</code>'s */
    private XMLBeanInfoRegistry registry = new DefaultXMLBeanInfoRegistry();
   
    /** Digester used to parse the XML descriptor files */
    private XMLBeanInfoDigester digester;

    /** Configuration to be used for introspection*/
    private IntrospectionConfiguration configuration;
   
    /** Base constructor */
    public XMLIntrospector() {
        this(new IntrospectionConfiguration());
    }
   
    /**
     * Construct allows a custom configuration to be set on construction.
     * This allows <code>IntrospectionConfiguration</code> subclasses
     * to be easily used.
     * @param configuration IntrospectionConfiguration, not null
     */
    public XMLIntrospector(IntrospectionConfiguration configuration) {
        setConfiguration(configuration);
    }
   
   
    // Properties
    //-------------------------------------------------------------------------  
   
    /**
     * <p>Gets the current logging implementation. </p>
     * @return the Log implementation which this class logs to
     */
    public Log getLog() {
        return getConfiguration().getIntrospectionLog();
    }

    /**
     * <p>Sets the current logging implementation.</p>
     * @param log the Log implementation to use for logging
     */
    public void setLog(Log log) {
        getConfiguration().setIntrospectionLog(log);
    }
   
    /**
     * <p>Gets the current registry implementation.
     * The registry is checked to see if it has an <code>XMLBeanInfo</code> for a class
     * before introspecting.
     * After standard introspection is complete, the instance will be passed to the registry.</p>
     *
     * <p>This allows finely grained control over the caching strategy.
     * It also allows the standard introspection mechanism
     * to be overridden on a per class basis.</p>
     *
     * @return the XMLBeanInfoRegistry currently used
     */
    public XMLBeanInfoRegistry getRegistry() {
        return registry;
    }
   
    /**
     * <p>Sets the <code>XMLBeanInfoRegistry</code> implementation.
     * The registry is checked to see if it has an <code>XMLBeanInfo</code> for a class
     * before introspecting.
     * After standard introspection is complete, the instance will be passed to the registry.</p>
     *
     * <p>This allows finely grained control over the caching strategy.
     * It also allows the standard introspection mechanism
     * to be overridden on a per class basis.</p>
     *
     * @param registry the XMLBeanInfoRegistry to use
     */
    public void setRegistry(XMLBeanInfoRegistry registry) {
        this.registry = registry;
    }
   
    /**
     * Gets the configuration to be used for introspection.
     * The various introspection-time strategies
     * and configuration variables have been consolidated as properties
     * of this bean.
     * This allows the configuration to be more easily shared.
     * @return IntrospectionConfiguration, not null
     */
    public IntrospectionConfiguration getConfiguration() {
        return configuration;
    }

    /**
     * Sets the configuration to be used for introspection.
     * The various introspection-time strategies
     * and configuration variables have been consolidated as properties
     * of this bean.
     * This allows the configuration to be more easily shared.
     * @param configuration IntrospectionConfiguration, not null
     */
    public void setConfiguration(IntrospectionConfiguration configuration) {
        this.configuration = configuration;
    }
   
   
    /**
      * Gets the <code>ClassNormalizer</code> strategy.
      * This is used to determine the Class to be introspected
      * (the normalized Class).
      *
      * @return the <code>ClassNormalizer</code> used to determine the Class to be introspected
      * for a given Object.
      * @deprecated 0.6 use getConfiguration().getClassNormalizer
      * @since 0.5
      */
    public ClassNormalizer getClassNormalizer() {
        return getConfiguration().getClassNormalizer();
    }
   
    /**
      * Sets the <code>ClassNormalizer</code> strategy.
      * This is used to determine the Class to be introspected
      * (the normalized Class).
      *
      * @param classNormalizer the <code>ClassNormalizer</code> to be used to determine
      * the Class to be introspected for a given Object.
      * @deprecated 0.6 use getConfiguration().setClassNormalizer
      * @since 0.5
      *
      */   
    public void setClassNormalizer(ClassNormalizer classNormalizer) {
        getConfiguration().setClassNormalizer(classNormalizer);
    }
   
    /**
     * Is <code>XMLBeanInfo</code> caching enabled?
     *
     * @deprecated 0.5 replaced by XMlBeanInfoRegistry
     * @return true if caching is enabled
     */
    public boolean isCachingEnabled() {
        return true;
    }

    /**
     * Set whether <code>XMLBeanInfo</code> caching should be enabled.
     *
     * @deprecated 0.5 replaced by XMlBeanInfoRegistry
     * @param cachingEnabled ignored
     */   
    public void setCachingEnabled(boolean cachingEnabled) {
        //
    }
    
   
    /**
      * Should attributes (or elements) be used for primitive types.
      * @return true if primitive types will be mapped to attributes in the introspection
      * @deprecated 0.6 use getConfiguration().isAttributesForPrimitives
      */
    public boolean isAttributesForPrimitives() {
        return getConfiguration().isAttributesForPrimitives();
    }

    /**
      * Set whether attributes (or elements) should be used for primitive types.
      * @param attributesForPrimitives pass trus to map primitives to attributes,
      *        pass false to map primitives to elements
      * @deprecated 0.6 use getConfiguration().setAttributesForPrimitives
      */
    public void setAttributesForPrimitives(boolean attributesForPrimitives) {
        getConfiguration().setAttributesForPrimitives(attributesForPrimitives);
    }

    /**
     * Should collections be wrapped in an extra element?
     *
     * @return whether we should we wrap collections in an extra element?
     * @deprecated 0.6 use getConfiguration().isWrapCollectionsInElement
     */
    public boolean isWrapCollectionsInElement() {
        return getConfiguration().isWrapCollectionsInElement();
    }

    /**
     * Sets whether we should we wrap collections in an extra element.
     *
     * @param wrapCollectionsInElement pass true if collections should be wrapped in a
     *        parent element
     * @deprecated 0.6 use getConfiguration().setWrapCollectionsInElement
     */
    public void setWrapCollectionsInElement(boolean wrapCollectionsInElement) {
        getConfiguration().setWrapCollectionsInElement(wrapCollectionsInElement);
    }

    /**
     * Get singular and plural matching strategy.
     *
     * @return the strategy used to detect matching singular and plural properties
     * @deprecated 0.6 use getConfiguration().getPluralStemmer
     */
    public PluralStemmer getPluralStemmer() {
        return getConfiguration().getPluralStemmer();
    }
   
    /**
     * Sets the strategy used to detect matching singular and plural properties
     *
     * @param pluralStemmer the PluralStemmer used to match singular and plural
     * @deprecated 0.6 use getConfiguration().setPluralStemmer
     */
    public void setPluralStemmer(PluralStemmer pluralStemmer) {
        getConfiguration().setPluralStemmer(pluralStemmer);
    }

    /**
     * Gets the name mapper strategy.
     *
     * @return the strategy used to convert bean type names into element names
     * @deprecated 0.5 getNameMapper is split up in
     * {@link #getElementNameMapper()} and {@link #getAttributeNameMapper()}
     */
    public NameMapper getNameMapper() {
        return getElementNameMapper();
    }
   
    /**
     * Sets the strategy used to convert bean type names into element names
     * @param nameMapper the NameMapper strategy to be used
     * @deprecated 0.5 setNameMapper is split up in
     * {@link #setElementNameMapper(NameMapper)} and {@link #setAttributeNameMapper(NameMapper)}
     */
    public void setNameMapper(NameMapper nameMapper) {
        setElementNameMapper(nameMapper);
    }


    /**
     * Gets the name mapping strategy used to convert bean names into elements.
     *
     * @return the strategy used to convert bean type names into element
     * names. If no element mapper is currently defined then a default one is created.
     * @deprecated 0.6 use getConfiguration().getElementNameMapper
     */
    public NameMapper getElementNameMapper() {
        return getConfiguration().getElementNameMapper();
    }
    
    /**
     * Sets the strategy used to convert bean type names into element names
     * @param nameMapper the NameMapper to use for the conversion
     * @deprecated 0.6 use getConfiguration().setElementNameMapper
     */
    public void setElementNameMapper(NameMapper nameMapper) {
        getConfiguration().setElementNameMapper( nameMapper );
    }
   

    /**
     * Gets the name mapping strategy used to convert bean names into attributes.
     *
     * @return the strategy used to convert bean type names into attribute
     * names. If no attributeNamemapper is known, it will default to the ElementNameMapper
     * @deprecated 0.6 getConfiguration().getAttributeNameMapper
     */
    public NameMapper getAttributeNameMapper() {
        return getConfiguration().getAttributeNameMapper();
     }


    /**
     * Sets the strategy used to convert bean type names into attribute names
     * @param nameMapper the NameMapper to use for the convertion
     * @deprecated 0.6 use getConfiguration().setAttributeNameMapper
     */
    public void setAttributeNameMapper(NameMapper nameMapper) {
        getConfiguration().setAttributeNameMapper( nameMapper );
    }
   
    /**
     * Should the original <code>java.reflect.Introspector</code> bean info search path be used?
     * By default it will be false.
     *
     * @return boolean if the beanInfoSearchPath should be used.
     * @deprecated 0.6 use getConfiguration().useBeanInfoSearchPath
     */
    public boolean useBeanInfoSearchPath() {
        return getConfiguration().useBeanInfoSearchPath();
    }

    /**
     * Specifies if you want to use the beanInfoSearchPath
     * @see java.beans.Introspector for more details
     * @param useBeanInfoSearchPath
     * @deprecated 0.6 use getConfiguration().setUseBeanInfoSearchPath
     */
    public void setUseBeanInfoSearchPath(boolean useBeanInfoSearchPath) {
        getConfiguration().setUseBeanInfoSearchPath( useBeanInfoSearchPath );
    }
   
    // Methods
    //-------------------------------------------------------------------------
   
    /**
     * Flush existing cached <code>XMLBeanInfo</code>'s.
     *
     * @deprecated 0.5 use flushable registry instead
     */
    public void flushCache() {}
   
   
    /** Create a standard <code>XMLBeanInfo</code> by introspection
      * The actual introspection depends only on the <code>BeanInfo</code>
      * associated with the bean.
      *
      * @param bean introspect this bean
      * @return XMLBeanInfo describing bean-xml mapping
      * @throws IntrospectionException when the bean introspection fails
      */
    public XMLBeanInfo introspect(Object bean) throws IntrospectionException {
        if (getLog().isDebugEnabled()) {
            getLog().debug( "Introspecting..." );
            getLog().debug(bean);
        }
       
        if ( bean instanceof DynaBean ) {
            // allow DynaBean implementations to be overridden by .betwixt files
            XMLBeanInfo xmlBeanInfo = findByXMLDescriptor( bean.getClass() );
            if (xmlBeanInfo != null) {
                return xmlBeanInfo;
            }
            // this is DynaBean use the DynaClass for introspection
            return introspect( ((DynaBean) bean).getDynaClass() );
           
        } else {
            // normal bean so normal introspection
            Class normalClass = getClassNormalizer().getNormalizedClass( bean );
            return introspect( normalClass );
        }
    }
   
    /**
     * Creates XMLBeanInfo by reading the DynaProperties of a DynaBean.
     * Customizing DynaBeans using betwixt is not supported.
     *
     * @param dynaClass the DynaBean to introspect
     *
     * @return XMLBeanInfo for the DynaClass
     */
    public XMLBeanInfo introspect(DynaClass dynaClass) {

        // for now this method does not do much, since XMLBeanInfoRegistry cannot
        // use a DynaClass as a key
        // TODO: add caching for DynaClass XMLBeanInfo
        // need to work out if this is possible
       
        // this line allows subclasses to change creation strategy
        XMLBeanInfo xmlInfo = createXMLBeanInfo( dynaClass );
       
        // populate the created info with
        DynaClassBeanType beanClass = new DynaClassBeanType( dynaClass );
        populate( xmlInfo, beanClass );
       
        return xmlInfo; 
    }
   
    /** Create a standard <code>XMLBeanInfo</code> by introspection.
      * The actual introspection depends only on the <code>BeanInfo</code>
      * associated with the bean.   
      *   
      * @param aClass introspect this class
      * @return XMLBeanInfo describing bean-xml mapping
      * @throws IntrospectionException when the bean introspection fails
      */
    public XMLBeanInfo introspect(Class aClass) throws IntrospectionException {
        // we first reset the beaninfo searchpath.
        String[] searchPath = null;
        if ( !getConfiguration().useBeanInfoSearchPath() ) {
            searchPath = Introspector.getBeanInfoSearchPath();
            Introspector.setBeanInfoSearchPath(new String[] { });
        }
       
        XMLBeanInfo xmlInfo = registry.get( aClass );
       
        if ( xmlInfo == null ) {
            // lets see if we can find an XML descriptor first
            if ( getLog().isDebugEnabled() ) {
                getLog().debug( "Attempting to lookup an XML descriptor for class: " + aClass );
            }
           
            xmlInfo = findByXMLDescriptor( aClass );
            if ( xmlInfo == null ) {
                BeanInfo info = Introspector.getBeanInfo( aClass );
                xmlInfo = introspect( info );
            }
           
            if ( xmlInfo != null ) {
                registry.put( aClass, xmlInfo );
            }
        } else {
            getLog().trace( "Used cached XMLBeanInfo." );
        }
       
        if ( getLog().isTraceEnabled() ) {
            getLog().trace( xmlInfo );
        }
        if ( !getConfiguration().useBeanInfoSearchPath() ) {
            // we restore the beaninfo searchpath.
            Introspector.setBeanInfoSearchPath( searchPath );
        }
       
        return xmlInfo;
    }
   
    /** Create a standard <code>XMLBeanInfo</code> by introspection.
      * The actual introspection depends only on the <code>BeanInfo</code>
      * associated with the bean.
      *
      * @param beanInfo the BeanInfo the xml-bean mapping is based on
      * @return XMLBeanInfo describing bean-xml mapping
      * @throws IntrospectionException when the bean introspection fails
      */
    public XMLBeanInfo introspect(BeanInfo beanInfo) throws IntrospectionException {   
        XMLBeanInfo xmlBeanInfo = createXMLBeanInfo( beanInfo );
        populate( xmlBeanInfo, new JavaBeanType( beanInfo ) );
        return xmlBeanInfo;
    }
   
    /**
     * Populates the given <code>XMLBeanInfo</code> based on the given type of bean.
     *
     * @param xmlBeanInfo populate this, not null
     * @param bean the type definition for the bean, not null
     */
    private void populate(XMLBeanInfo xmlBeanInfo, BeanType bean) {   
        String name = bean.getBeanName();
       
        ElementDescriptor elementDescriptor = new ElementDescriptor();
        elementDescriptor.setLocalName(
            getElementNameMapper().mapTypeToElementName( name ) );
        elementDescriptor.setPropertyType( bean.getElementType() );
       
        if (getLog().isTraceEnabled()) {
            getLog().trace("Populating:" + bean);
        }

        // add default string value for primitive types
        if ( bean.isPrimitiveType() ) {
            getLog().trace("Bean is primitive");
            elementDescriptor.setTextExpression( StringExpression.getInstance() );
           
        } else if ( bean.isLoopType() ) {
            getLog().trace("Bean is loop");
            ElementDescriptor loopDescriptor = new ElementDescriptor();
            loopDescriptor.setContextExpression(
                new IteratorExpression( EmptyExpression.getInstance() )
            );
            if ( bean.isMapType() ) {
                loopDescriptor.setQualifiedName( "entry" );
            }
            elementDescriptor.setElementDescriptors( new ElementDescriptor[] { loopDescriptor } );
           
        } else {
            getLog().trace("Bean is standard type");
            List elements = new ArrayList();
            List attributes = new ArrayList();
            List contents = new ArrayList();

            addProperties( bean.getProperties(), elements, attributes, contents );   

            int size = elements.size();
            if ( size > 0 ) {
                ElementDescriptor[] descriptors = new ElementDescriptor[size];
                elements.toArray( descriptors );
                elementDescriptor.setElementDescriptors( descriptors );
            }
            size = attributes.size();
            if ( size > 0 ) {
                AttributeDescriptor[] descriptors = new AttributeDescriptor[size];
                attributes.toArray( descriptors );
                elementDescriptor.setAttributeDescriptors( descriptors );
            }
            size = contents.size();
            if ( size > 0 ) {
                if ( size > 0 ) {
                    Descriptor[] descriptors = new Descriptor[size];
                    contents.toArray( descriptors );
                    elementDescriptor.setContentDescriptors( descriptors );
                }
            }
        }
       
        xmlBeanInfo.setElementDescriptor( elementDescriptor );       
       
        // default any addProperty() methods
        defaultAddMethods( elementDescriptor, bean.getElementType() );
       
        if (getLog().isTraceEnabled()) {
            getLog().trace("Populated descriptor:");
            getLog().trace(elementDescriptor);
        }
    }

   
    /**
     * Creates XMLBeanInfo for the given DynaClass.
     *
     * @param dynaClass the class describing a DynaBean
     *
     * @return XMLBeanInfo that describes the properties of the given
     * DynaClass
     */
    protected XMLBeanInfo createXMLBeanInfo(DynaClass dynaClass) {
        // XXX is the chosen class right?
        XMLBeanInfo beanInfo = new XMLBeanInfo(dynaClass.getClass());
        return beanInfo;
    }




    /**
     * Create a XML descriptor from a bean one.
     * Go through and work out whether it's a loop property, a primitive or a standard.
     * The class property is ignored.
     *
     * @param propertyDescriptor create a <code>NodeDescriptor</code> for this property
     * @param useAttributesForPrimitives write primitives as attributes (rather than elements)
     * @return a correctly configured <code>NodeDescriptor</code> for the property
     * @throws IntrospectionException when bean introspection fails
     * @deprecated 0.5 use {@link #createXMLDescriptor}.
     */
    public Descriptor createDescriptor(
        PropertyDescriptor propertyDescriptor,
        boolean useAttributesForPrimitives
    ) throws IntrospectionException {
        return createXMLDescriptor( new BeanProperty( propertyDescriptor ) );
    }
    /**
     * Create a XML descriptor from a bean one.
     * Go through and work out whether it's a loop property, a primitive or a standard.
     * The class property is ignored.
     *
     * @param beanProperty the BeanProperty specifying the property
     * @return a correctly configured <code>NodeDescriptor</code> for the property
     * @since 0.5
     */
    public Descriptor createXMLDescriptor( BeanProperty beanProperty ) {
        return beanProperty.createXMLDescriptor( configuration );
    }


    /**
     * Add any addPropety(PropertyType) methods as Updaters
     * which are often used for 1-N relationships in beans.
     * <br>
     * The tricky part here is finding which ElementDescriptor corresponds
     * to the method. e.g. a property 'items' might have an Element descriptor
     * which the method addItem() should match to.
     * <br>
     * So the algorithm we'll use
     * by default is to take the decapitalized name of the property being added
     * and find the first ElementDescriptor that matches the property starting with
     * the string. This should work for most use cases.
     * e.g. addChild() would match the children property.
     * <br>
     * TODO this probably needs refactoring. It probably belongs in the bean wrapper
     * (so that it'll work properly with dyna-beans) and so that the operations can
     * be optimized by caching. Multiple hash maps are created and getMethods is
     * called multiple times. This is relatively expensive and so it'd be better
     * to push into a proper class and cache.
     * <br>
     * TODO this probably does work properly with DynaBeans: need to push
     * implementation into an class and expose it on BeanType.
     *
     * @param introspector use this <code>XMLIntrospector</code> for introspection
     * @param rootDescriptor add defaults to this descriptor
     * @param beanClass the <code>Class</code> to which descriptor corresponds
     */
    public void defaultAddMethods(
                                            ElementDescriptor rootDescriptor,
                                            Class beanClass ) {
                                             
        // lets iterate over all methods looking for one of the form
        // add*(PropertyType)
        if ( beanClass != null ) {
            ArrayList singleParameterAdders = new ArrayList();
            ArrayList twinParameterAdders = new ArrayList();
           
            Method[] methods = beanClass.getMethods();
            for ( int i = 0, size = methods.length; i < size; i++ ) {
                Method method = methods[i];
                String name = method.getName();
                if ( name.startsWith( "add" )) {
                    // TODO: should we filter out non-void returning methods?
                    // some beans will return something as a helper
                    Class[] types = method.getParameterTypes();
                    if ( types != null) {
                        if ( getLog().isTraceEnabled() ) {
                            getLog().trace("Searching for match for " + method);
                        }
                       
                        switch (types.length)
                        {
                            case 1:
                                singleParameterAdders.add(method);
                                break;
                            case 2:
                                twinParameterAdders.add(method);
                                break;
                            default:
                                // ignore
                                break;
                        }
                    }
                }
            }
           
            Map elementsByPropertyName = makeElementDescriptorMap( rootDescriptor );
           
            for (Iterator it=singleParameterAdders.iterator();it.hasNext();) {
                Method singleParameterAdder = (Method) it.next();
                setIteratorAdder(elementsByPropertyName, singleParameterAdder);
            }
           
            for (Iterator it=twinParameterAdders.iterator();it.hasNext();) {
                Method twinParameterAdder = (Method) it.next();
                setMapAdder(elementsByPropertyName, twinParameterAdder);
            }
        }
    }
   
    /**
     * Sets the adder method where the corresponding property is an iterator
     * @param rootDescriptor
     * @param singleParameterAdder
     */
    private void setIteratorAdder(
        Map elementsByPropertyName,
        Method singleParameterAdderMethod) {
       
        String adderName = singleParameterAdderMethod.getName();
        String propertyName = Introspector.decapitalize(adderName.substring(3));
        ElementDescriptor matchingDescriptor = getMatchForAdder(propertyName, elementsByPropertyName);
        if (matchingDescriptor != null) {
            //TODO defensive code: probably should check descriptor type
           
            Class singularType = singleParameterAdderMethod.getParameterTypes()[0];
            if (getLog().isTraceEnabled()) {
                getLog().trace(adderName + "->" + propertyName);
            }
            // this may match a standard collection or iteration
            getLog().trace("Matching collection or iteration");
                                   
            matchingDescriptor.setUpdater( new MethodUpdater( singleParameterAdderMethod ) );
            matchingDescriptor.setSingularPropertyType( singularType );
            matchingDescriptor.setHollow(!isPrimitiveType(singularType));
            String localName = matchingDescriptor.getLocalName();
            if ( localName == null || localName.length() == 0 ) {
                matchingDescriptor.setLocalName(
                    getElementNameMapper()
                        .mapTypeToElementName( propertyName ) );
            }
                                   
            if ( getLog().isDebugEnabled() ) {
                getLog().debug( "!! " + singleParameterAdderMethod);
                getLog().debug( "!! " + singularType);
            }
        }
    }
   
    /**
     * Sets the adder where the corresponding property type is an map
     * @param rootDescriptor
     * @param singleParameterAdder
     */
    private void setMapAdder(
        Map elementsByPropertyName,
        Method twinParameterAdderMethod) {
        String adderName = twinParameterAdderMethod.getName();
        String propertyName = Introspector.decapitalize(adderName.substring(3));
        ElementDescriptor matchingDescriptor = getMatchForAdder(propertyName, elementsByPropertyName);
        if ( matchingDescriptor != null
            && Map.class.isAssignableFrom( matchingDescriptor.getPropertyType() )) {
            // this may match a map
            getLog().trace("Matching map");
            ElementDescriptor[] children
                = matchingDescriptor.getElementDescriptors();
            // see if the descriptor's been set up properly
            if ( children.length == 0 ) {                                       
                getLog().info(
                    "'entry' descriptor is missing for map. "
                    + "Updaters cannot be set");
                                       
            } else {
                Class[] types = twinParameterAdderMethod.getParameterTypes();
                Class keyType = types[0];
                Class valueType = types[1];
               
                // loop through children
                // adding updaters for key and value
                MapEntryAdder adder = new MapEntryAdder(twinParameterAdderMethod);
                for (
                    int n=0,
                        noOfGrandChildren = children.length;
                    n < noOfGrandChildren;
                    n++ ) {
                    if ( "key".equals( children[n].getLocalName() ) ) {
                                     
                        children[n].setUpdater( adder.getKeyUpdater() );
                        children[n].setSingularPropertyTypekeyType );
                        if (children[n].getPropertyType() == null) {
                            children[n].setPropertyType( valueType );
                        }
                        if ( isPrimitiveType(keyType) ) {
                            children[n].setHollow(false);
                        }
                        if ( getLog().isTraceEnabled() ) {
                            getLog().trace( "Key descriptor: " + children[n]);
                        }                                              
                                               
                    } else if ( "value".equals( children[n].getLocalName() ) ) {

                        children[n].setUpdater( adder.getValueUpdater() );
                        children[n].setSingularPropertyType( valueType );
                        if (children[n].getPropertyType() == null) {
                            children[n].setPropertyType( valueType );
                        }
                        if ( isPrimitiveType( valueType) ) {
                            children[n].setHollow(false);
                        }
                        if ( isLoopType( valueType )) {
                            // need to attach a hollow descriptor
                            // don't know the element name
                            // so use null name (to match anything)
                            ElementDescriptor loopDescriptor = new ElementDescriptor();
                            loopDescriptor.setHollow(true);
                            loopDescriptor.setSingularPropertyType( valueType );
                            loopDescriptor.setPropertyType( valueType );
                            children[n].addElementDescriptor(loopDescriptor);
                           
                        }
                        if ( getLog().isTraceEnabled() ) {
                            getLog().trace( "Value descriptor: " + children[n]);
                        }
                    }
                }
            }      
        }
    }
       
    /**
     * Gets an ElementDescriptor for the property matching the adder
     * @param adderName
     * @param rootDescriptor
     * @return
     */
    private ElementDescriptor getMatchForAdder(
                                                String propertyName,
                                                Map elementsByPropertyName) {
        ElementDescriptor matchingDescriptor = null;
        if (propertyName.length() > 0) {
            if ( getLog().isTraceEnabled() ) {
                getLog().trace( "findPluralDescriptor( " + propertyName
                    + " ):root property name=" + propertyName );
            }
       
            PluralStemmer stemmer = getPluralStemmer();
            matchingDescriptor = stemmer.findPluralDescriptor( propertyName, elementsByPropertyName );
       
            if ( getLog().isTraceEnabled() ) {
                getLog().trace(
                    "findPluralDescriptor( " + propertyName
                        + " ):ElementDescriptor=" + matchingDescriptor );
            }
        }
        return matchingDescriptor;
    }
   
    // Implementation methods
    //-------------------------------------------------------------------------
        

    /**
     * Creates a map where the keys are the property names and the values are the ElementDescriptors
     */
    private Map makeElementDescriptorMap( ElementDescriptor rootDescriptor ) {
        Map result = new HashMap();
        String rootPropertyName = rootDescriptor.getPropertyName();
        if (rootPropertyName != null) {
            result.put(rootPropertyName, rootDescriptor);
        }
        makeElementDescriptorMap( rootDescriptor, result );
        return result;
    }
   
    /**
     * Creates a map where the keys are the property names and the values are the ElementDescriptors
     *
     * @param rootDescriptor the values of the maps are the children of this
     * <code>ElementDescriptor</code> index by their property names
     * @param map the map to which the elements will be added
     */
    private void makeElementDescriptorMap( ElementDescriptor rootDescriptor, Map map ) {
        ElementDescriptor[] children = rootDescriptor.getElementDescriptors();
        if ( children != null ) {
            for ( int i = 0, size = children.length; i < size; i++ ) {
                ElementDescriptor child = children[i];               
                String propertyName = child.getPropertyName();               
                if ( propertyName != null ) {
                    map.put( propertyName, child );
                }
                makeElementDescriptorMap( child, map );
            }
        }
    }
   
    /**
     * A Factory method to lazily create a new strategy
     * to detect matching singular and plural properties.
     *
     * @return new defualt PluralStemmer implementation
     * @deprecated 0.6 this method has been moved into IntrospectionConfiguration.
     * Those who need to vary this should subclass that class instead
     */
    protected PluralStemmer createPluralStemmer() {
        return new DefaultPluralStemmer();
    }
   
    /**
     * A Factory method to lazily create a strategy
     * used to convert bean type names into element names.
     *
     * @return new default NameMapper implementation
     * @deprecated 0.6 this method has been moved into IntrospectionConfiguration.
     * Those who need to vary this should subclass that class instead
     */
    protected NameMapper createNameMapper() {
        return new DefaultNameMapper();
    }
   
    /**
     * Attempt to lookup the XML descriptor for the given class using the
     * classname + ".betwixt" using the same ClassLoader used to load the class
     * or return null if it could not be loaded
     *
     * @param aClass digester .betwixt file for this class
     * @return XMLBeanInfo digested from the .betwixt file if one can be found.
     *         Otherwise null.
     */
    protected synchronized XMLBeanInfo findByXMLDescriptor( Class aClass ) {
        // trim the package name
        String name = aClass.getName();
        int idx = name.lastIndexOf( '.' );
        if ( idx >= 0 ) {
            name = name.substring( idx + 1 );
        }
        name += ".betwixt";
       
        URL url = aClass.getResource( name );
        if ( url != null ) {
            try {
                String urlText = url.toString();
                if ( getLog().isDebugEnabled( )) {
                    getLog().debug( "Parsing Betwixt XML descriptor: " + urlText );
                }
                // synchronized method so this digester is only used by
                // one thread at once
                if ( digester == null ) {
                    digester = new XMLBeanInfoDigester();
                    digester.setXMLIntrospector( this );
                }
                digester.setBeanClass( aClass );
                return (XMLBeanInfo) digester.parse( urlText );
            } catch (Exception e) {
                getLog().warn( "Caught exception trying to parse: " + name, e );
            }
        }
       
        if ( getLog().isTraceEnabled() ) {
            getLog().trace( "Could not find betwixt file " + name );
        }
        return null;
    }
           
    /**
     * Loop through properties and process each one
     *
     * @param beanInfo the BeanInfo whose properties will be processed
     * @param elements ElementDescriptor list to which elements will be added
     * @param attributes AttributeDescriptor list to which attributes will be added
     * @param contents Descriptor list to which mixed content will be added
     * @throws IntrospectionException if the bean introspection fails
     * @deprecated 0.5 use {@link #addProperties(BeanProperty[], List, List,List)}
     */
    protected void addProperties(
                                    BeanInfo beanInfo,
                                    List elements,
                                    List attributes,
                                    List contents)
                                        throws
                                            IntrospectionException {
        PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
        if ( descriptors != null ) {
            for ( int i = 0, size = descriptors.length; i < size; i++ ) {
                addProperty(beanInfo, descriptors[i], elements, attributes, contents);
            }
        }
        if (getLog().isTraceEnabled()) {
            getLog().trace(elements);
            getLog().trace(attributes);
            getLog().trace(contents);
        }
    }
    /**
     * Loop through properties and process each one
     *
     * @param beanProperties the properties to be processed
     * @param elements ElementDescriptor list to which elements will be added
     * @param attributes AttributeDescriptor list to which attributes will be added
     * @param contents Descriptor list to which mixed content will be added
     * @since 0.5
     */
    protected void addProperties(
                                    BeanProperty[] beanProperties,
                                    List elements,
                                    List attributes,
                                    List contents) {
        if ( beanProperties != null ) {
            if (getLog().isTraceEnabled()) {
                getLog().trace(beanProperties.length + " properties to be added");
            }
            for ( int i = 0, size = beanProperties.length; i < size; i++ ) {
                addProperty(beanProperties[i], elements, attributes, contents);
            }
        }
        if (getLog().isTraceEnabled()) {
            getLog().trace("After properties have been added (elements, attributes, contents):");
            getLog().trace(elements);
            getLog().trace(attributes);
            getLog().trace(contents);
        }
    }   

   
    /**
     * Process a property.
     * Go through and work out whether it's a loop property, a primitive or a standard.
     * The class property is ignored.
     *
     * @param beanInfo the BeanInfo whose property is being processed
     * @param propertyDescriptor the PropertyDescriptor to process
     * @param elements ElementDescriptor list to which elements will be added
     * @param attributes AttributeDescriptor list to which attributes will be added
     * @param contents Descriptor list to which mixed content will be added
     * @throws IntrospectionException if the bean introspection fails
     * @deprecated 0.5 BeanInfo is no longer required.
     * Use {@link #addProperty(PropertyDescriptor, List, List, List)} instead.
     */
    protected void addProperty(
                                BeanInfo beanInfo,
                                PropertyDescriptor propertyDescriptor,
                                List elements,
                                List attributes,
                                List contents)
                                    throws
                                        IntrospectionException {
       addProperty( propertyDescriptor, elements, attributes, contents);
    }
   
    /**
     * Process a property.
     * Go through and work out whether it's a loop property, a primitive or a standard.
     * The class property is ignored.
     *
     * @param propertyDescriptor the PropertyDescriptor to process
     * @param elements ElementDescriptor list to which elements will be added
     * @param attributes AttributeDescriptor list to which attributes will be added
     * @param contents Descriptor list to which mixed content will be added
     * @throws IntrospectionException if the bean introspection fails
     * @deprecated 0.5 use {@link #addProperty(BeanProperty, List, List, List)} instead
     */
    protected void addProperty(
                                PropertyDescriptor propertyDescriptor,
                                List elements,
                                List attributes,
                                List contents)
                                    throws
                                        IntrospectionException {
        addProperty(new BeanProperty( propertyDescriptor ), elements, attributes, contents);
    }
   
    /**
     * Process a property.
     * Go through and work out whether it's a loop property, a primitive or a standard.
     * The class property is ignored.
     *
     * @param beanProperty the bean property to process
     * @param elements ElementDescriptor list to which elements will be added
     * @param attributes AttributeDescriptor list to which attributes will be added
     * @param contents Descriptor list to which mixed content will be added
     * @since 0.5
     */
    protected void addProperty(
                                BeanProperty beanProperty,
                                List elements,
                                List attributes,
                                List contents) {
        Descriptor nodeDescriptor = createXMLDescriptor(beanProperty);
        if (nodeDescriptor == null) {
           return;
        }
        if (nodeDescriptor instanceof ElementDescriptor) {
           elements.add(nodeDescriptor);
        } else if (nodeDescriptor instanceof AttributeDescriptor) {
           attributes.add(nodeDescriptor);
        } else {
           contents.add(nodeDescriptor);
        }                                
    }
   
    /**
     * Loop through properties and process each one
     *
     * @param beanInfo the BeanInfo whose properties will be processed
     * @param elements ElementDescriptor list to which elements will be added
     * @param attributes AttributeDescriptor list to which attributes will be added
     * @throws IntrospectionException if the bean introspection fails
     * @deprecated 0.5 this method does not support mixed content.
     * Use {@link #addProperties(BeanInfo, List, List, List)} instead.
     */
    protected void addProperties(
                                    BeanInfo beanInfo,
                                    List elements,
                                    List attributes)
                                        throws
                                            IntrospectionException {
        PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
        if ( descriptors != null ) {
            for ( int i = 0, size = descriptors.length; i < size; i++ ) {
                addProperty(beanInfo, descriptors[i], elements, attributes);
            }
        }
        if (getLog().isTraceEnabled()) {
            getLog().trace(elements);
            getLog().trace(attributes);
        }
    }
   
    /**
     * Process a property.
     * Go through and work out whether it's a loop property, a primitive or a standard.
     * The class property is ignored.
     *
     * @param beanInfo the BeanInfo whose property is being processed
     * @param propertyDescriptor the PropertyDescriptor to process
     * @param elements ElementDescriptor list to which elements will be added
     * @param attributes AttributeDescriptor list to which attributes will be added
     * @throws IntrospectionException if the bean introspection fails
     * @deprecated 0.5 this method does not support mixed content.
     * Use {@link #addProperty(BeanInfo, PropertyDescriptor, List, List, List)} instead.
     */
    protected void addProperty(
                                BeanInfo beanInfo,
                                PropertyDescriptor propertyDescriptor,
                                List elements,
                                List attributes)
                                    throws
                                        IntrospectionException {
        NodeDescriptor nodeDescriptor = XMLIntrospectorHelper
            .createDescriptor(propertyDescriptor,
                                 isAttributesForPrimitives(),
                                 this);
        if (nodeDescriptor == null) {
           return;
        }
        if (nodeDescriptor instanceof ElementDescriptor) {
           elements.add(nodeDescriptor);
        } else {
           attributes.add(nodeDescriptor);
        }
    }

   
    /**
     * Factory method to create XMLBeanInfo instances
     *
     * @param beanInfo the BeanInfo from which the XMLBeanInfo will be created
     * @return XMLBeanInfo describing the bean-xml mapping
     */
    protected XMLBeanInfo createXMLBeanInfo( BeanInfo beanInfo ) {
        XMLBeanInfo xmlBeanInfo = new XMLBeanInfo( beanInfo.getBeanDescriptor().getBeanClass() );
        return xmlBeanInfo;
    }

    /**
     * Is this class a loop?
     *
     * @param type the Class to test
     * @return true if the type is a loop type
     */
    public boolean isLoopType(Class type) {
        return XMLIntrospectorHelper.isLoopType(type);
    }
   
   
    /**
     * Is this class a primitive?
     * TODO: this method will probably be deprecated when primitive types
     * are subsumed into the simple type concept
     * @param type the Class to test
     * @return true for primitive types
     */
    public boolean isPrimitiveType(Class type) {
        TypeBindingStrategy.BindingType bindingType
      = configuration.getTypeBindingStrategy().bindingType( type ) ;
        boolean result = (bindingType.equals(TypeBindingStrategy.BindingType.PRIMITIVE));
        return result;
    }

   
    /** Some type of pseudo-bean */
    private abstract class BeanType {
        /**
         * Gets the name for this bean type
         * @return the bean type name, not null
         */
        public abstract String getBeanName();
       
        /**
         * Gets the type to be used by the associated element
         * @return a Class that is the type not null
         */
        public abstract Class getElementType();

        /**
         * Is this type a primitive?
         * @return true if this type should be treated by betwixt as a primitive
         */
        public abstract boolean isPrimitiveType();
       
        /**
         * is this type a map?
         * @return true this should be treated as a map.
         */
        public abstract boolean isMapType();
       
        /**
         * Is this type a loop?
         * @return true if this should be treated as a loop
         */
        public abstract boolean isLoopType();
       
        /**
         * Gets the properties associated with this bean.
         * @return the BeanProperty's, not null
         */
        public abstract BeanProperty[] getProperties();
       
        /**
         * Create string representation
         * @return something useful for logging
         */
        public String toString() {
            return "Bean[name=" + getBeanName() + ", type=" + getElementType();
        }
    }
   
    /** Supports standard Java Beans */
    private class JavaBeanType extends BeanType {
        /** Introspected bean */
        private BeanInfo beanInfo;
        /** Bean class */
        private Class beanClass;
        /** Bean name */
        private String name;
        /** Bean properties */
        private BeanProperty[] properties;
       
        /**
         * Constructs a BeanType for a standard Java Bean
         * @param beanInfo the BeanInfo describing the standard Java Bean, not null
         */
        public JavaBeanType(BeanInfo beanInfo) {
            this.beanInfo = beanInfo;
            BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
            beanClass = beanDescriptor.getBeanClass();
            name = beanDescriptor.getName();
            // Array's contain a bad character
            if (beanClass.isArray()) {
                // called all array's Array
                name = "Array";
            }
           
        }
       
        /** @see BeanType #getElementType */
        public Class getElementType() {
            return beanClass;
        }
       
        /** @see BeanType#getBeanName */
        public String getBeanName() {
            return name;
        }
       
        /** @see BeanType#isPrimitiveType */
        public boolean isPrimitiveType() {
            return XMLIntrospector.this.isPrimitiveType( beanClass );
        }
       
        /** @see BeanType#isLoopType */
        public boolean isLoopType() {
            return XMLIntrospectorHelper.isLoopType( beanClass );
        }
       
        /** @see BeanType#isMapType */
        public boolean isMapType() {
            return Map.class.isAssignableFrom( beanClass );
        }
       
        /** @see BeanType#getProperties */
        public BeanProperty[] getProperties() {
            // lazy creation
            if ( properties == null ) {
                ArrayList propertyDescriptors = new ArrayList();
                // add base bean info
                PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
                if ( descriptors != null ) {
                    for (int i=0, size=descriptors.length; i<size; i++) {
                        propertyDescriptors.add( descriptors[i] );
                    }
                }
               
                // add properties from additional bean infos
                BeanInfo[] additionals = beanInfo.getAdditionalBeanInfo();
                if ( additionals != null ) {
                    for ( int i=0, outerSize=additionals.length; i<outerSize; i++ ) {
                        BeanInfo additionalInfo = additionals[i];
                        descriptors = beanInfo.getPropertyDescriptors();
                        if ( descriptors != null ) {
                            for (int j=0, innerSize=descriptors.length; j<innerSize; j++) {
                                propertyDescriptors.add( descriptors[j] );
                            }
                        }
                    }           
                }
                // what happens when size is zero?
                properties = new BeanProperty[ propertyDescriptors.size() ];
                int count = 0;
                for ( Iterator it = propertyDescriptors.iterator(); it.hasNext(); count++) {
                    PropertyDescriptor propertyDescriptor = (PropertyDescriptor) it.next();
                    properties[count] = new BeanProperty( propertyDescriptor );
                }
            }
            return properties;
        }
    }
   
    /** Implementation for DynaClasses */
    private class DynaClassBeanType extends BeanType {
        /** BeanType for this DynaClass */
        private DynaClass dynaClass;
        /** Properties extracted in constuctor */
        private BeanProperty[] properties;
       
        /**
         * Constructs a BeanType for a DynaClass
         * @param dynaClass not null
         */
        public DynaClassBeanType(DynaClass dynaClass) {
            this.dynaClass = dynaClass;
            DynaProperty[] dynaProperties = dynaClass.getDynaProperties();
            properties = new BeanProperty[dynaProperties.length];
            for (int i=0, size=dynaProperties.length; i<size; i++) {
                properties[i] = new BeanProperty(dynaProperties[i]);
            }
        }
       
        /** @see BeanType#getBeanName */
        public String getBeanName() {
            return dynaClass.getName();
        }
        /** @see BeanType#getElementType */
        public Class getElementType() {
            return DynaClass.class;
        }
        /** @see BeanType#isPrimitiveType */
        public boolean isPrimitiveType() {
            return false;
        }
        /** @see BeanType#isMapType */
        public boolean isMapType() {
            return false;
        }
        /** @see BeanType#isLoopType */
        public boolean isLoopType() {
            return false;
        }
        /** @see BeanType#getProperties */
        public BeanProperty[] getProperties() {
            return properties;
        }
    }
}
TOP

Related Classes of org.apache.commons.betwixt.XMLIntrospector$DynaClassBeanType

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.