Package org.apache.commons.betwixt.digester

Source Code of org.apache.commons.betwixt.digester.XMLIntrospectorHelper

package org.apache.commons.betwixt.digester;

/*
* 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.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.betwixt.AttributeDescriptor;
import org.apache.commons.betwixt.ElementDescriptor;
import org.apache.commons.betwixt.NodeDescriptor;
import org.apache.commons.betwixt.XMLIntrospector;
import org.apache.commons.betwixt.expression.IteratorExpression;
import org.apache.commons.betwixt.expression.MapEntryAdder;
import org.apache.commons.betwixt.expression.MethodExpression;
import org.apache.commons.betwixt.expression.MethodUpdater;
import org.apache.commons.betwixt.strategy.PluralStemmer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
  * <p><code>XMLIntrospectorHelper</code> a helper class for
  * common code shared between the digestor and introspector.</p>
  *
  * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
  * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
  * @version $Id: XMLIntrospectorHelper.java,v 1.29.2.1 2004/06/19 16:24:10 rdonkin Exp $
  */
public class XMLIntrospectorHelper {

    /** Log used for logging (Doh!) */
    protected static Log log = LogFactory.getLog( XMLIntrospectorHelper.class );
   
    /** Base constructor */
    public XMLIntrospectorHelper() {
    }
   
    /**
     * <p>Gets the current logging implementation.</p>
     *
     * @return current log
     */
    public static Log getLog() {
        return log;
    }

    /**
     * <p>Sets the current logging implementation.</p>
     *
     * @param aLog use this <code>Log</code>
     */
    public static void setLog(Log aLog) {
        log = aLog;
    }
   


    /**
     * 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 create a <code>NodeDescriptor</code> for this property
     * @param useAttributesForPrimitives write primitives as attributes (rather than elements)
     * @param introspector use this <code>XMLIntrospector</code>
     * @return a correctly configured <code>NodeDescriptor</code> for the property
     * @throws IntrospectionException when bean introspection fails
     * @deprecated 0.5 this method has been replaced by {@link XMLIntrospector#createDescriptor}
     */
    public static NodeDescriptor createDescriptor(
        PropertyDescriptor propertyDescriptor,
        boolean useAttributesForPrimitives,
        XMLIntrospector introspector
    ) throws IntrospectionException {
        String name = propertyDescriptor.getName();
        Class type = propertyDescriptor.getPropertyType();
      
        if (log.isTraceEnabled()) {
            log.trace("Creating descriptor for property: name="
                + name + " type=" + type);
        }
       
        NodeDescriptor nodeDescriptor = null;
        Method readMethod = propertyDescriptor.getReadMethod();
        Method writeMethod = propertyDescriptor.getWriteMethod();
       
        if ( readMethod == null ) {
            if (log.isTraceEnabled()) {
                log.trace( "No read method for property: name="
                    + name + " type=" + type);
            }
            return null;
        }
       
        if ( log.isTraceEnabled() ) {
            log.trace( "Read method=" + readMethod.getName() );
        }
       
        // choose response from property type
       
        // XXX: ignore class property ??
        if ( Class.class.equals( type ) && "class".equals( name ) ) {
            log.trace( "Ignoring class property" );
            return null;
        }
        if ( isPrimitiveType( type ) ) {
            if (log.isTraceEnabled()) {
                log.trace( "Primitive type: " + name);
            }
            if ( useAttributesForPrimitives ) {
                if (log.isTraceEnabled()) {
                    log.trace( "Adding property as attribute: " + name );
                }
                nodeDescriptor = new AttributeDescriptor();
            } else {
                if (log.isTraceEnabled()) {
                    log.trace( "Adding property as element: " + name );
                }
                nodeDescriptor = new ElementDescriptor(true);
            }
            nodeDescriptor.setTextExpression( new MethodExpression( readMethod ) );
           
            if ( writeMethod != null ) {
                nodeDescriptor.setUpdater( new MethodUpdater( writeMethod ) );
            }
        } else if ( isLoopType( type ) ) {
            if (log.isTraceEnabled()) {
                log.trace("Loop type: " + name);
                log.trace("Wrap in collections? " + introspector.isWrapCollectionsInElement());
            }
            ElementDescriptor loopDescriptor = new ElementDescriptor();
            loopDescriptor.setContextExpression(
                new IteratorExpression( new MethodExpression( readMethod ) )
            );
            loopDescriptor.setWrapCollectionsInElement(
                        introspector.isWrapCollectionsInElement());
            // XXX: need to support some kind of 'add' or handle arrays, Lists or indexed properties
            //loopDescriptor.setUpdater( new MethodUpdater( writeMethod ) );
            if ( Map.class.isAssignableFrom( type ) ) {
                loopDescriptor.setQualifiedName( "entry" );
                // add elements for reading
                loopDescriptor.addElementDescriptor( new ElementDescriptor( "key" ) );
                loopDescriptor.addElementDescriptor( new ElementDescriptor( "value" ) );
            }

            ElementDescriptor elementDescriptor = new ElementDescriptor();
            elementDescriptor.setWrapCollectionsInElement(
                        introspector.isWrapCollectionsInElement());
            elementDescriptor.setElementDescriptors( new ElementDescriptor[] { loopDescriptor } );
           
            nodeDescriptor = elementDescriptor;           
        } else {
            if (log.isTraceEnabled()) {
                log.trace( "Standard property: " + name);
            }
            ElementDescriptor elementDescriptor = new ElementDescriptor();
            elementDescriptor.setContextExpression( new MethodExpression( readMethod ) );
            if ( writeMethod != null ) {
                elementDescriptor.setUpdater( new MethodUpdater( writeMethod ) );
            }
           
            nodeDescriptor = elementDescriptor;         
        }

        if (nodeDescriptor instanceof AttributeDescriptor) {
            // we want to use the attributemapper only when it is an attribute..
            nodeDescriptor.setLocalName(
                introspector.getAttributeNameMapper().mapTypeToElementName( name ) );
        } else {
            nodeDescriptor.setLocalName(
                introspector.getElementNameMapper().mapTypeToElementName( name ) );
        }       
 
        nodeDescriptor.setPropertyName( propertyDescriptor.getName() );
        nodeDescriptor.setPropertyType( type );       
       
        // XXX: associate more bean information with the descriptor?
        //nodeDescriptor.setDisplayName( propertyDescriptor.getDisplayName() );
        //nodeDescriptor.setShortDescription( propertyDescriptor.getShortDescription() );
       
        if (log.isTraceEnabled()) {
            log.trace("Created descriptor:");
            log.trace(nodeDescriptor);
        }
        return nodeDescriptor;
    }
   
    /**
     * Configure an <code>ElementDescriptor</code> from a <code>PropertyDescriptor</code>.
     * This uses default element updater (the write method of the property).
     *
     * @param elementDescriptor configure this <code>ElementDescriptor</code>
     * @param propertyDescriptor configure from this <code>PropertyDescriptor</code>
     */
    public static void configureProperty(
                                    ElementDescriptor elementDescriptor,
                                    PropertyDescriptor propertyDescriptor ) {
                                   
        configureProperty( elementDescriptor, propertyDescriptor, null, null);
    }
                                   
    /**
     * Configure an <code>ElementDescriptor</code> from a <code>PropertyDescriptor</code>.
     * A custom update method may be set.
     *
     * @param elementDescriptor configure this <code>ElementDescriptor</code>
     * @param propertyDescriptor configure from this <code>PropertyDescriptor</code>
     * @param updateMethodName the name of the custom updater method to user.
     * If null, then then
     * @param beanClass the <code>Class</code> from which the update method should be found.
     * This may be null only when <code>updateMethodName</code> is also null.
     * @since 0.5
     */
    public static void configureProperty(
                                    ElementDescriptor elementDescriptor,
                                    PropertyDescriptor propertyDescriptor,
                                    String updateMethodName,
                                    Class beanClass ) {
       
        Class type = propertyDescriptor.getPropertyType();
        Method readMethod = propertyDescriptor.getReadMethod();
        Method writeMethod = propertyDescriptor.getWriteMethod();
       
        elementDescriptor.setLocalName( propertyDescriptor.getName() );
        elementDescriptor.setPropertyType( type );       
       
        // XXX: associate more bean information with the descriptor?
        //nodeDescriptor.setDisplayName( propertyDescriptor.getDisplayName() );
        //nodeDescriptor.setShortDescription( propertyDescriptor.getShortDescription() );
       
        if ( readMethod == null ) {
            log.trace( "No read method" );
            return;
        }
       
        if ( log.isTraceEnabled() ) {
            log.trace( "Read method=" + readMethod.getName() );
        }
       
        // choose response from property type
       
        // XXX: ignore class property ??
        if ( Class.class.equals( type ) && "class".equals( propertyDescriptor.getName() ) ) {
            log.trace( "Ignoring class property" );
            return;
        }
        if ( isPrimitiveType( type ) ) {
            elementDescriptor.setTextExpression( new MethodExpression( readMethod ) );
            elementDescriptor.setPrimitiveType(true);
        } else if ( isLoopType( type ) ) {
            log.trace("Loop type ??");
           
            // don't wrap this in an extra element as its specified in the
            // XML descriptor so no need.           
            elementDescriptor.setContextExpression(
                new IteratorExpression( new MethodExpression( readMethod ) )
            );

            writeMethod = null;
        } else {
            log.trace( "Standard property" );
            elementDescriptor.setContextExpression( new MethodExpression( readMethod ) );
        }
   
        // see if we have a custom method update name
        if (updateMethodName == null) {
            // set standard write method
            if ( writeMethod != null ) {
                elementDescriptor.setUpdater( new MethodUpdater( writeMethod ) );
            }
           
        } else {
            // see if we can find and set the custom method
            if ( log.isTraceEnabled() ) {
                log.trace( "Finding custom method: " );
                log.trace( "  on:" + beanClass );
                log.trace( "  name:" + updateMethodName );
            }
           
            Method updateMethod = null;
            Method[] methods = beanClass.getMethods();
            for ( int i = 0, size = methods.length; i < size; i++ ) {
                Method method = methods[i];
                if ( updateMethodName.equals( method.getName() ) ) {
                    // we have a matching name
                    // check paramters are correct
                    if (methods[i].getParameterTypes().length == 1) {
                        // we'll use first match
                        updateMethod = methods[i];
                        if ( log.isTraceEnabled() ) {
                            log.trace("Matched method:" + updateMethod);
                        }
                        // done since we're using the first match
                        break;
                    }
                }
            }
           
            if (updateMethod == null) {
                if ( log.isInfoEnabled() ) {
                   
                    log.info("No method with name '" + updateMethodName + "' found for update");
                }
            } else {
   
                elementDescriptor.setUpdater( new MethodUpdater( updateMethod ) );
                elementDescriptor.setSingularPropertyType( updateMethod.getParameterTypes()[0] );
                if ( log.isTraceEnabled() ) {
                    log.trace( "Set custom updater on " + elementDescriptor);
                }
            }
        }
    }
   
    /**
     * Configure an <code>AttributeDescriptor</code> from a <code>PropertyDescriptor</code>
     *
     * @param attributeDescriptor configure this <code>AttributeDescriptor</code>
     * @param propertyDescriptor configure from this <code>PropertyDescriptor</code>
     */
    public static void configureProperty(
                                    AttributeDescriptor attributeDescriptor,
                                    PropertyDescriptor propertyDescriptor ) {
        Class type = propertyDescriptor.getPropertyType();
        Method readMethod = propertyDescriptor.getReadMethod();
        Method writeMethod = propertyDescriptor.getWriteMethod();
       
        if ( readMethod == null ) {
            log.trace( "No read method" );
            return;
        }
       
        if ( log.isTraceEnabled() ) {
            log.trace( "Read method=" + readMethod );
        }
       
        // choose response from property type
       
        // XXX: ignore class property ??
        if ( Class.class.equals( type ) && "class".equals( propertyDescriptor.getName() ) ) {
            log.trace( "Ignoring class property" );
            return;
        }
        if ( isLoopType( type ) ) {
            log.warn( "Using loop type for an attribute. Type = "
                    + type.getName() + " attribute: " + attributeDescriptor.getQualifiedName() );
        }

        log.trace( "Standard property" );
        attributeDescriptor.setTextExpression( new MethodExpression( readMethod ) );
       
        if ( writeMethod != null ) {
            attributeDescriptor.setUpdater( new MethodUpdater( writeMethod ) );
        }
       
        attributeDescriptor.setLocalName( propertyDescriptor.getName() );
        attributeDescriptor.setPropertyType( type );       
       
        // XXX: associate more bean information with the descriptor?
        //nodeDescriptor.setDisplayName( propertyDescriptor.getDisplayName() );
        //nodeDescriptor.setShortDescription( propertyDescriptor.getShortDescription() );
    }
   

    /**
     * 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.
     *
     * @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 static void defaultAddMethods(
                                            XMLIntrospector introspector,
                                            ElementDescriptor rootDescriptor,
                                            Class beanClass ) {
        // lets iterate over all methods looking for one of the form
        // add*(PropertyType)
        if ( beanClass != null ) {
            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" ) ) {
                    // XXX: should we filter out non-void returning methods?
                    // some beans will return something as a helper
                    Class[] types = method.getParameterTypes();
                    if ( types != null) {
                        if ( log.isTraceEnabled() ) {
                            log.trace("Searching for match for " + method);
                        }
                       
                        if ( ( types.length == 1 ) || types.length == 2 ) {
                            String propertyName = Introspector.decapitalize( name.substring(3) );
                            if (propertyName.length() == 0)
                                continue;
                            if ( log.isTraceEnabled() ) {
                                log.trace( name + "->" + propertyName );
                            }
   
                            // now lets try find the ElementDescriptor which displays
                            // a property which starts with propertyName
                            // and if so, we'll set a new Updater on it if there
                            // is not one already
                            ElementDescriptor descriptor =
                                findGetCollectionDescriptor(
                                                            introspector,
                                                            rootDescriptor,
                                                            propertyName );
   
                            if ( log.isDebugEnabled() ) {
                                log.debug( "!! " + propertyName + " -> " + descriptor );
                                log.debug( "!! " + name + " -> "
                                + (descriptor!=null?descriptor.getPropertyName():"") );
                            }
                            if ( descriptor != null ) {
                                boolean isMapDescriptor
                                    = Map.class.isAssignableFrom( descriptor.getPropertyType() );
                                if ( !isMapDescriptor && types.length == 1 ) {
                                    // this may match a standard collection or iteration
                                    log.trace("Matching collection or iteration");
                                   
                                    descriptor.setUpdater( new MethodUpdater( method ) );
                                    descriptor.setSingularPropertyType( types[0] );
                                   
                                    if ( log.isDebugEnabled() ) {
                                        log.debug( "!! " + method);
                                        log.debug( "!! " + types[0]);
                                    }
                                   
                                    // is there a child element with no localName
                                    ElementDescriptor[] children
                                        = descriptor.getElementDescriptors();
                                    if ( children != null && children.length > 0 ) {
                                        ElementDescriptor child = children[0];
                                        String localName = child.getLocalName();
                                        if ( localName == null || localName.length() == 0 ) {
                                            child.setLocalName(
                                                introspector.getElementNameMapper()
                                                    .mapTypeToElementName( propertyName ) );
                                        }
                                    }

                                } else if ( isMapDescriptor && types.length == 2 ) {
                                    // this may match a map
                                    log.trace("Matching map");
                                    ElementDescriptor[] children
                                        = descriptor.getElementDescriptors();
                                    // see if the descriptor's been set up properly
                                    if ( children.length == 0 ) {
                                       
                                        log.info(
                                            "'entry' descriptor is missing for map. "
                                            + "Updaters cannot be set");
                                       
                                    } else {
                                        // loop through grandchildren
                                        // adding updaters for key and value
                                        ElementDescriptor[] grandchildren
                                            = children[0].getElementDescriptors();
                                        MapEntryAdder adder = new MapEntryAdder(method);
                                        for (
                                            int n=0,
                                                noOfGrandChildren = grandchildren.length;
                                            n < noOfGrandChildren;
                                            n++ ) {
                                            if ( "key".equals(
                                                    grandchildren[n].getLocalName() ) ) {
                                           
                                                grandchildren[n].setUpdater(
                                                                adder.getKeyUpdater() );
                                                grandchildren[n].setSingularPropertyType(
                                                                types[0] );
                                                if ( log.isTraceEnabled() ) {
                                                    log.trace(
                                                        "Key descriptor: " + grandchildren[n]);
                                                }                                              
                                               
                                            } else if (
                                                "value".equals(
                                                    grandchildren[n].getLocalName() ) ) {

                                                grandchildren[n].setUpdater(
                                                                    adder.getValueUpdater() );
                                                grandchildren[n].setSingularPropertyType(
                                                                    types[1] );
                                                if ( log.isTraceEnabled() ) {
                                                    log.trace(
                                                        "Value descriptor: " + grandchildren[n]);
                                                }
                                            }
                                        }
                                    }
                                }
                            } else {
                                if ( log.isDebugEnabled() ) {
                                    log.debug(
                                        "Could not find an ElementDescriptor with property name: "
                                        + propertyName + " to attach the add method: " + method
                                    );
                                }
                            }
                        }
                    }
                }
            }
        }
    }
   
    /**
     * Is this a loop type class?
     *
     * @param type is this <code>Class</code> a loop type?
     * @return true if the type is a loop type, or if type is null
     */
    public static boolean isLoopType(Class type) {
        // check for NPEs
        if (type == null) {
            log.trace("isLoopType: type is null");
            return false;
        }
        return type.isArray()
            || Map.class.isAssignableFrom( type )
            || Collection.class.isAssignableFrom( type )
            || Enumeration.class.isAssignableFrom( type )
            || Iterator.class.isAssignableFrom( type );
    }
   
   
    /**
     * Is this a primitive type?
     *
     * @param type is this <code>Class<code> a primitive type?
     * @return true for primitive types
     */
    public static boolean isPrimitiveType(Class type) {
        if ( type == null ) {
            return false;
           
        } else if ( type.isPrimitive() ) {
            return true;
           
        } else if ( type.equals( Object.class ) ) {
            return false;
        }
        return type.getName().startsWith( "java.lang." )
            || Number.class.isAssignableFrom( type )
            || String.class.isAssignableFrom( type )
            || Date.class.isAssignableFrom( type )
            || java.sql.Date.class.isAssignableFrom( type )
            || java.sql.Time.class.isAssignableFrom( type )
            || java.sql.Timestamp.class.isAssignableFrom( type )
            || java.math.BigDecimal.class.isAssignableFrom( type )
            || java.math.BigInteger.class.isAssignableFrom( type );
    }
   
    // Implementation methods
    //-------------------------------------------------------------------------   
   
    /**
     * Attempts to find the element descriptor for the getter property that
     * typically matches a collection or array. The property name is used
     * to match. e.g. if an addChild() method is detected the
     * descriptor for the 'children' getter property should be returned.
     *
     * @param introspector use this <code>XMLIntrospector</code>
     * @param rootDescriptor the <code>ElementDescriptor</code> whose child element will be
     * searched for a match
     * @param propertyName the name of the 'adder' method to match
     * @return <code>ElementDescriptor</code> for the matching getter
     */
    protected static ElementDescriptor findGetCollectionDescriptor(
                                                XMLIntrospector introspector,
                                                ElementDescriptor rootDescriptor,
                                                String propertyName ) {
        // create the Map of propertyName -> descriptor that the PluralStemmer will choose
        Map map = new HashMap();
        //String propertyName = rootDescriptor.getPropertyName();
        if ( log.isTraceEnabled() ) {
            log.trace( "findPluralDescriptor( " + propertyName
                + " ):root property name=" + rootDescriptor.getPropertyName() );
        }
       
        if (rootDescriptor.getPropertyName() != null) {
            map.put(propertyName, rootDescriptor);
        }
        makeElementDescriptorMap( rootDescriptor, map );
       
        PluralStemmer stemmer = introspector.getPluralStemmer();
        ElementDescriptor elementDescriptor = stemmer.findPluralDescriptor( propertyName, map );
       
        if ( log.isTraceEnabled() ) {
            log.trace(
                "findPluralDescriptor( " + propertyName
                    + " ):ElementDescriptor=" + elementDescriptor );
        }
       
        return elementDescriptor;
    }

    /**
     * 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
     */
    protected static 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 );
            }
        }
    }

    /**
     * Traverse the tree of element descriptors and find the oldValue and swap it with the newValue.
     * This would be much easier to do if ElementDescriptor supported a parent relationship.
     *
     * @param rootDescriptor traverse child graph for this <code>ElementDescriptor</code>
     * @param oldValue replace this <code>ElementDescriptor</code>
     * @param newValue replace with this <code>ElementDescriptor</code>
     */    
    protected static void swapDescriptor(
                                ElementDescriptor rootDescriptor,
                                ElementDescriptor oldValue,
                                ElementDescriptor newValue ) {
        ElementDescriptor[] children = rootDescriptor.getElementDescriptors();
        if ( children != null ) {
            for ( int i = 0, size = children.length; i < size; i++ ) {
                ElementDescriptor child = children[i];
                if ( child == oldValue ) {
                    children[i] = newValue;
                    break;
                }
                swapDescriptor( child, oldValue, newValue );
            }
        }
    }
}
TOP

Related Classes of org.apache.commons.betwixt.digester.XMLIntrospectorHelper

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.