Package com.cloudhopper.commons.xbean

Source Code of com.cloudhopper.commons.xbean.XmlBean

package com.cloudhopper.commons.xbean;

/*
* #%L
* ch-commons-xbean
* %%
* Copyright (C) 2012 Cloudhopper by Twitter
* %%
* 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.
* #L%
*/

// java imports
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.io.InputStream;
import org.xml.sax.SAXException;

// my imports
import com.cloudhopper.commons.util.BeanProperty;
import com.cloudhopper.commons.util.BeanUtil;
import com.cloudhopper.commons.util.ClassUtil;
import com.cloudhopper.commons.util.StringUtil;
import com.cloudhopper.commons.xml.XPath;
import com.cloudhopper.commons.xml.XmlParser;
import com.cloudhopper.commons.xml.XmlParser.Attribute;
import java.io.File;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;

/**
* Represents a Java bean configured via XML. This class essentially is a much
* more simple version of Spring. Java objects are loosely configured with this
* class using reflection and method/field names matching elements in an xml
* document.
* <br><br>
* An element in an xml document represents a property that will be set on a
* Java object.  This class supports nested Java objects and their configuration
* as well. Nested objects are first retrieved via a getter. If they are null,
* this class will create a new instance with the default empty constructor.
* <br><br>
* Java objects can first be programmatically configured via Java code followed
* by overrides in an xml document. Or, Java objects can be configured over and
* over again by multiple xml documents.  Also, a subset of "xpath" is supported
* to allow elements inside an xml document to become the "root" of the xml document.
* <br><br>
* Also, a Java object is permitted to throw exceptions when a property is set
* and that exception will be rethrown inside a PropertyInvocationException.
* Properties are converted to their Java equivalents based on the Class type
* of the property of the Java object.
* <br><br>
* <b>Sample Java Classes:</b>
* <pre>{@code
* public class Server {
*   public void setPort(int port) { this.port = value; }
*   public void setHost(String value) { this.host = value; }
* }
*
* public class Config {
*   public void setServer(Server value) { this.server = value; }
* }}</pre>
*
* <b>Sample XML:</b>
* <pre>{@code
* <configuration>
*   <server>
*     <host>www.google.com</host>
*     <port>80</port>
*   </server>
* </configuration>
* }</pre>
*
* <b>Sample Client Java Code:</b>
* <pre>{@code
* XmlBean bean = new XmlBean();
* Config config = new Config();
* bean.configure(xml, config);
* }</pre>
*
* @author joelauer
*/
public class XmlBean {

    private String rootTag;
    private boolean accessPrivateProperties;

    public XmlBean() {
        rootTag = null;
        accessPrivateProperties = false;
    }

    /**
     * If non-null, will check that the root tag's value matches this value.
     * @param value The root node tag's value to match such as "Configuration"
     */
    public void setRootTag(String value) {
        this.rootTag = value;
    }

    /**
     * Controls if private properties (without associated public getter/setter method)
     * are okay to set on a bean.  If false, then a specific permission exception
     * will be thrown. This option is false by default.
     * @param value True if underlying fields should be exposed, otherwise set to false.
     */
    public void setAccessPrivateProperties(boolean value) {
        this.accessPrivateProperties = value;
    }

    /**
     * Configures the object by setting the value of its properties with the
     * values from the xml document.  Each element of the xml document represents
     * a named property of the object.  For example, if "setFirstName" is a property
     * of the object, then an element of "<firstName>Joe</firstName>" would set
     * that value to "Joe".
     * @param xml A string representation of the xml document
     * @param obj The object to configure
     * @throws com.cloudhopper.commons.xbean.XmlBeanException Thrown if an exception
     *      occurs while configuring the object.
     * @throws java.io.IOException Thrown if an exception occurs while attempting
     *      to parse the input for the xml document.
     * @throws org.xml.sax.SAXException Thrown if an exception occurs while parsing
     *      the xml document.
     */
    public void configure(String xml, Object obj) throws XmlBeanException, IOException, SAXException {
        configure(xml, obj, null);
    }

    /**
     * Parses the xml document and configures the object by setting the value of its properties with the
     * values from the xml document.  Each element of the xml document represents
     * a named property of the object.  For example, if "setFirstName" is a property
     * of the object, then an element of "<firstName>Joe</firstName>" would set
     * that value to "Joe".
     * @param xml A string representation of the xml document
     * @param obj The object to configure
     * @param xpath The xpath used to select a new "root" node when configuring
     *      the object. For example, "/nodeA/nodeB" would effectively configure
     *      the object starting as though nodeB was the root node. Set to null
     *      to ignore using the xpath.
     * @throws com.cloudhopper.commons.xbean.XPathNotFoundException Thrown if the
     *      provided xpath isn't found in the xml document.
     * @throws com.cloudhopper.commons.xbean.XmlBeanException Thrown if an exception
     *      occurs while configuring the object.
     * @throws java.io.IOException Thrown if an exception occurs while attempting
     *      to parse the input for the xml document.
     * @throws org.xml.sax.SAXException Thrown if an exception occurs while parsing
     *      the xml document.
     */
    public void configure(String xml, Object obj, String xpath) throws XPathNotFoundException, XmlBeanException, IOException, SAXException {
        XmlParser parser = new XmlParser();
        XmlParser.Node rootNode = parser.parse(xml);
        configure(rootNode, obj, xpath);
    }

    /**
     * Parses the xml document and configures the object by setting the value of its properties with the
     * values from the xml document.  Each element of the xml document represents
     * a named property of the object.  For example, if "setFirstName" is a property
     * of the object, then an element of "<firstName>Joe</firstName>" would set
     * that value to "Joe".
     * @param in An inputstream that contains the xml document
     * @param obj The object to configure
     * @throws com.cloudhopper.commons.xbean.XPathNotFoundException Thrown if the
     *      provided xpath isn't found in the xml document.
     * @throws com.cloudhopper.commons.xbean.XmlBeanException Thrown if an exception
     *      occurs while configuring the object.
     * @throws java.io.IOException Thrown if an exception occurs while attempting
     *      to parse the input for the xml document.
     * @throws org.xml.sax.SAXException Thrown if an exception occurs while parsing
     *      the xml document.
     */
    public void configure(InputStream in, Object obj) throws XmlBeanException, IOException, SAXException {
        configure(in, obj, null);
    }

    /**
     * Parses the xml document and configures the object by setting the value of its properties with the
     * values from the xml document.  Each element of the xml document represents
     * a named property of the object.  For example, if "setFirstName" is a property
     * of the object, then an element of "<firstName>Joe</firstName>" would set
     * that value to "Joe".
     * @param in An inputstream that contains the xml document
     * @param obj The object to configure
     * @param xpath The xpath used to select a new "root" node when configuring
     *      the object. For example, "/nodeA/nodeB" would effectively configure
     *      the object starting as though nodeB was the root node. Set to null
     *      to ignore using the xpath.
     * @throws com.cloudhopper.commons.xbean.XPathNotFoundException Thrown if the
     *      provided xpath isn't found in the xml document.
     * @throws com.cloudhopper.commons.xbean.XmlBeanException Thrown if an exception
     *      occurs while configuring the object.
     * @throws java.io.IOException Thrown if an exception occurs while attempting
     *      to parse the input for the xml document.
     * @throws org.xml.sax.SAXException Thrown if an exception occurs while parsing
     *      the xml document.
     */
    public void configure(InputStream in, Object obj, String xpath) throws XPathNotFoundException, XmlBeanException, IOException, SAXException {
        XmlParser parser = new XmlParser();
        XmlParser.Node rootNode = parser.parse(in);
        configure(rootNode, obj);
    }

    /**
     * Parses the xml document and configures the object by setting the value of its properties with the
     * values from the xml document.  Each element of the xml document represents
     * a named property of the object.  For example, if "setFirstName" is a property
     * of the object, then an element of "<firstName>Joe</firstName>" would set
     * that value to "Joe".
     * @param file The file object containing the xml document
     * @param obj The object to configure
     * @throws com.cloudhopper.commons.xbean.XPathNotFoundException Thrown if the
     *      provided xpath isn't found in the xml document.
     * @throws com.cloudhopper.commons.xbean.XmlBeanException Thrown if an exception
     *      occurs while configuring the object.
     * @throws java.io.IOException Thrown if an exception occurs while attempting
     *      to parse the input for the xml document.
     * @throws org.xml.sax.SAXException Thrown if an exception occurs while parsing
     *      the xml document.
     */
    public void configure(File file, Object obj) throws XmlBeanException, IOException, SAXException {
        configure(file, obj, null);
    }

    /**
     * Parses the xml document and configures the object by setting the value of its properties with the
     * values from the xml document.  Each element of the xml document represents
     * a named property of the object.  For example, if "setFirstName" is a property
     * of the object, then an element of "<firstName>Joe</firstName>" would set
     * that value to "Joe".
     * @param file The file object containing the xml document
     * @param obj The object to configure
     * @param xpath The xpath used to select a new "root" node when configuring
     *      the object. For example, "/nodeA/nodeB" would effectively configure
     *      the object starting as though nodeB was the root node. Set to null
     *      to ignore using the xpath.
     * @throws com.cloudhopper.commons.xbean.XPathNotFoundException Thrown if the
     *      provided xpath isn't found in the xml document.
     * @throws com.cloudhopper.commons.xbean.XmlBeanException Thrown if an exception
     *      occurs while configuring the object.
     * @throws java.io.IOException Thrown if an exception occurs while attempting
     *      to parse the input for the xml document.
     * @throws org.xml.sax.SAXException Thrown if an exception occurs while parsing
     *      the xml document.
     */
    public void configure(File file, Object obj, String xpath) throws XPathNotFoundException, XmlBeanException, IOException, SAXException {
        XmlParser parser = new XmlParser();
        XmlParser.Node rootNode = parser.parse(file);
        configure(rootNode, obj);
    }

    /**
     * Configures the object by setting the value of its properties with the
     * values from the xml document.  Each element of the xml document represents
     * a named property of the object.  For example, if "setFirstName" is a property
     * of the object, then an element of "<firstName>Joe</firstName>" would set
     * that value to "Joe".
     * @param rootNode The root node of the xml document
     * @param obj The object to configure
     * @param xpath The xpath used to select a new "root" node when configuring
     *      the object. For example, "/nodeA/nodeB" would effectively configure
     *      the object starting as though nodeB was the root node. Set to null
     *      to ignore using the xpath.
     * @throws com.cloudhopper.commons.xbean.XPathNotFoundException Thrown if the
     *      provided xpath isn't found in the xml document.
     * @throws com.cloudhopper.commons.xbean.XmlBeanException Thrown if an exception
     *      occurs while configuring the object.
     */
    public void configure(XmlParser.Node rootNode, Object obj, String xpath) throws XPathNotFoundException, XmlBeanException {
        // if xpath isn't null
        if (xpath != null) {
            // attempt to select a new rootNode using the xpath
            rootNode = XPath.select(rootNode, xpath);

            // if node is null, then this xpath wasn't found
            if (rootNode == null) {
                throw new XPathNotFoundException("Xpath '" + xpath + "' not found from root node");
            }
        }
        // delegate to configure method
        configure(rootNode, obj);
    }

    /**
     * Configures the object by setting the value of its properties with the
     * values from the xml document.  Each element of the xml document represents
     * a named property of the object.  For example, if "setFirstName" is a property
     * of the object, then an element of "<firstName>Joe</firstName>" would set
     * that value to "Joe".
     * @param rootNode The root node of the xml document
     * @param obj The object to configure
     * @throws com.cloudhopper.commons.xbean.XmlBeanException Thrown if an exception
     *      occurs while configuring the object.
     */
    public void configure(XmlParser.Node rootNode, Object obj) throws XmlBeanException {
        // root node doesn't matter -- unless we're going to validate its name
        if (this.rootTag != null) {
            if (!this.rootTag.equals(rootNode.getTag())) {
                throw new RootTagMismatchException("Root tag mismatch [expected=" + this.rootTag + ", actual=" + rootNode.getTag() + "]");
            }
        }

        // were any attributes included in the root?
        if (rootNode.hasAttributes()) {
            throw new PropertyNoAttributesExpectedException("[root node]", rootNode.getPath(), obj.getClass(), "No xml attributes expected for root node");
        }

        // create a hashmap to track properties
        HashMap<String,String> properties = new HashMap<String,String>();

        doConfigure(rootNode, obj, properties, true, null);
    }

    /**
     * Internal method for handling the configuration of an object. This method
     * is recursively called for simple and complex properties.
     */
    private void doConfigure(XmlParser.Node rootNode, Object obj, HashMap<String,String> properties, boolean checkForDuplicates, CollectionHelper ch) throws XmlBeanException {
        // loop thru all child nodes
        if (rootNode.hasChildren()) {

            for (XmlParser.Node node : rootNode.getChildren()) {
               
                // tag name represents a property we're going to set
                String propertyName = node.getTag();

                // find the property if it exists
                BeanProperty property = null;
               
                if (ch != null) {
                    // make sure node tag name matches propertyName
                    if (!propertyName.equals(ch.getValueProperty().getName())) {
                        throw new PropertyNotFoundException(propertyName, node.getPath(), obj.getClass(), "Collection can only be configured with a property name of [" + ch.getValueProperty().getName() + "] but [" + propertyName + "] was used instead");
                    }
                    property = ch.getValueProperty();
                } else {
                    try {
                        property = BeanUtil.findBeanProperty(obj.getClass(), propertyName, true);
                    } catch (IllegalAccessException e) {
                        throw new PropertyPermissionException(propertyName, node.getPath(), obj.getClass(), "Illegal access while attempting to reflect property from class", e);
                    }
                }

                // if property is null, then this isn't a valid property on this object
                if (property == null) {
                    throw new PropertyNotFoundException(propertyName, node.getPath(), obj.getClass(), "Property [" + propertyName + "] not found");
                }

                // only some attributes are permitted if we're dealing with a
                // collection or map at this point
                boolean isCollection = (Collection.class.isAssignableFrom(property.getType()));
                boolean isMap = (Map.class.isAssignableFrom(property.getType()));
               
                // were any attributes included?
                String typeAttrString = null;
                String valueAttrString = null;
                String keyAttrString = null;
               
                // check if an annotation is present for the field
                if (property.getField() != null) {
                    XmlBeanProperty annotation = property.getField().getAnnotation(XmlBeanProperty.class);
                    if (annotation != null) {
                        if (!StringUtil.isEmpty(annotation.value())) {
                            valueAttrString = annotation.value();
                        }
                        if (!StringUtil.isEmpty(annotation.key())) {
                            keyAttrString = annotation.key();
                        }
                    }
                }
               
                // process attributes within the xml itself
                if (node.hasAttributes()) {
                    for (Attribute attr : node.getAttributes()) {
                        if (attr.getName().equals("type")) {
                            typeAttrString = attr.getValue();
                        } else if (attr.getName().equals("value") && (isCollection || isMap)) {
                            // only permitted on collections or map
                            valueAttrString = attr.getValue();
                        } else if (attr.getName().equals("key") && (isMap || (ch != null && ch.isMapType()))) {
                            // only permitted on map type OR on a map value
                            keyAttrString = attr.getValue();
                        } else {
                            throw new PropertyNoAttributesExpectedException(propertyName, node.getPath(), obj.getClass(), "One or more attributes not allowed for property [" + propertyName + "]");
                        }
                    }
                }
               
                //
                // otherwise, the property exists, attempt to set it
                //

                // is there actually a "setter" method -- we shouldn't let
                // user's be able to configure fields in this case
                // unless accessing private properties is allowed
                // unless a collection helper is also null
                if (ch == null && !this.accessPrivateProperties && property.getAddMethod() == null && property.getSetMethod() == null) {
                    throw new PropertyPermissionException(propertyName, node.getPath(), obj.getClass(), "Not permitted to add or set property [" + propertyName + "]");
                }

                // if we can "add" this property, then turn off checkForDuplicates
                // we also don't check for duplicates in the case of a collection
                if (ch != null || property.canAdd()) {
                    checkForDuplicates = false;
                }
               
                // was this property already previously set?
                // only use this check if an "add" method doesn't exist for the bean
                if (checkForDuplicates && properties.containsKey(node.getPath())) {
                    throw new PropertyAlreadySetException(propertyName, node.getPath(), obj.getClass(), "Property [" + propertyName + "] was already previously set in the xml");
                }
                // add this property to our hashmap
                properties.put(node.getPath(), null);

               
                // if a "type" attribute was included - check that it both exists
                // and is compatible with the type of the property it is being added/set to
                Class typeAttrClass = null;
                if (typeAttrString != null) {
                    try {
                        typeAttrClass = Class.forName(typeAttrString);
                    } catch (ClassNotFoundException e) {
                        throw new PropertyInvalidTypeException(propertyName, node.getPath(), obj.getClass(), "Unable to find class [" + typeAttrString + "] specified in type attribute of property '" + propertyName + "'");
                    }
                   
                    if (!property.getType().isAssignableFrom(typeAttrClass)) {
                        throw new PropertyInvalidTypeException(propertyName, node.getPath(), obj.getClass(), "Unable to assign a value of specified type [" + typeAttrString + "] to property [" + propertyName + "] which is a type [" + property.getType().getName() + "]");
                    }
                }
               
                // the object we'll eventually add or set
                Object value = null;
                // get the node's text value
                String nodeText = node.getText();
               
                // is this a simple conversion?
                if (TypeConverterUtil.isSupported(property.getType())) {
                    // was any text set?  if not, throw an exception
                    if (nodeText == null) {
                        throw new PropertyIsEmptyException(propertyName, node.getPath(), obj.getClass(), "Value for property [" + propertyName + "] was empty in xml");
                    }

                    // try to convert this to a Java object value
                    try {
                        value = TypeConverterUtil.convert(nodeText, property.getType());
                    } catch (ConversionException e) {
                        throw new PropertyConversionException(propertyName, node.getPath(), obj.getClass(), "The value [" + nodeText + "] for property [" + propertyName + "] could not be converted to a(n) " + property.getType().getSimpleName() + ". " + e.getMessage());
                    }

                // otherwise, this is a "complicated" type
                } else {

                    // only "get" the property if its possible -- e.g. if there
                    // is only an addXXXX method available, then this would throw
                    // an exception, so we'll check to see if getting the property
                    // is possible first
                    if (property.canGet()) {
                        try {
                            value = property.get(obj);
                        } catch (IllegalAccessException e) {
                            throw new PropertyPermissionException(propertyName, node.getPath(), obj.getClass(), "Illegal access while attempting to get property value from object", e);
                        } catch (InvocationTargetException e) {
                            Throwable t = e;
                            // this generally means the setXXXX method on the object
                            // threw an exception -- we want to unwrap that and just
                            // return that exception instead
                            if (e.getCause() != null) {
                                t = e.getCause();
                            }
                            throw new PropertyInvocationException(propertyName, node.getPath(), obj.getClass(), "The existing value for property [" + propertyName + "] caused an exception during get", t.getMessage(), t);
                        }
                    }
                   
                    // if null, then we need to create a new instance of it
                    if (value == null) {
                        Class newType = property.getType();
                        // create a new instance of either the actual type OR the
                        // type specified in the "type" attribute
                        if (typeAttrClass != null) {
                            newType = typeAttrClass;
                        }
                       
                        try {
                            value = newType.newInstance();
                        } catch (InstantiationException e) {
                            throw new XmlBeanClassException("Failed while attempting to create object of type " + newType.getName(), e);
                        } catch (IllegalAccessException e) {
                            throw new PropertyPermissionException(propertyName, node.getPath(), obj.getClass(), "Illegal access while attempting to create new instance of " + newType.getName(), e);
                        }
                    }
                   
                    // special handling for "collections" -- required for handling
                    // the values in configuring of child objects
                    CollectionHelper newch = null;
                    if (value instanceof Collection || value instanceof Map) {
                        newch = createCollectionHelper(node, obj, value, propertyName, property, valueAttrString, keyAttrString);
                    }

                    // recursively configure the next object
                    doConfigure(node, value, properties, checkForDuplicates, newch);
                }
               
                // save this reference object back (since it was successfully configured)
                if (ch != null) {
                    if (ch.isCollectionType()) {
                        ch.getCollectionObject().add(value);
                    } else if (ch.isMapType()) {
                        // need to figure out the key value -- it may either be a value from the value OR a simple type
                        Object keyValue = null;
                        if (ch.getKeyProperty() == null) {
                            // a KEY must have been set!
                            if (StringUtil.isEmpty(keyAttrString)) {
                                throw new PropertyIsEmptyException(propertyName, node.getPath(), obj.getClass(), "The XML attribute [key] was null or empty and is required");
                            } else {
                                try {
                                    keyValue = TypeConverterUtil.convert(keyAttrString, ch.getKeyClass());
                                } catch (ConversionException e) {
                                    throw new PropertyConversionException(propertyName, node.getPath(), obj.getClass(), "Unable to cleanly convert key value [" + keyAttrString + "] into type [" + ch.getKeyClass().getName() + ": " + e.getMessage(), e);
                                }
                            }
                        } else {
                            try {
                                // extract the key value from the object
                                keyValue = ch.getKeyProperty().get(value);
                            } catch (Exception e) {
                                throw new PropertyPermissionException(propertyName, node.getPath(), value.getClass(), "Unable to access property to get the value of the key: " + e.getMessage(), e);
                            }
                           
                            if (keyValue == null) {
                                throw new PropertyIsEmptyException(propertyName, node.getPath(), obj.getClass(), "The value of the key [" + ch.getKeyProperty().getName() + "] was null; unable to put value onto the map");
                            }
                        }

                        ch.getMapObject().put(keyValue, value);
                    } else {
                        throw new PropertyPermissionException(propertyName, node.getPath(), obj.getClass(), "Unsupported collection/map type used");
                    }
                } else {
                    try {
                        property.addOrSet(obj, value);
                    } catch (InvocationTargetException e) {
                        Throwable t = e;
                        // this generally means the setXXXX method on the object
                        // threw an exception -- we want to unwrap that and just
                        // return that exception instead
                        if (e.getCause() != null) {
                            t = e.getCause();
                        }
                        throw new PropertyInvocationException(propertyName, node.getPath(), obj.getClass(), "The value '" + nodeText + "' for property '" + propertyName + "' caused an exception", t.getMessage(), t);
                    } catch (IllegalAccessException e) {
                        throw new PropertyPermissionException(propertyName, node.getPath(), obj.getClass(), "Illegal access while setting property", e);
                    }
                }
            }
        }
    }
   
    public CollectionHelper createCollectionHelper(XmlParser.Node node, Object obj, Object value, String propertyName, BeanProperty property, String valueAttrString, String keyAttrString) throws PropertyPermissionException, XmlBeanClassException {
        // special handling for "collections"
        CollectionHelper newch = null;
       
        // determine "propertyName" for elements within the collection
        String valuePropertyName = (valueAttrString != null ? valueAttrString : "value");

        // determine type of objects to create by determining generic type
        if (property.getField() == null) {
            throw new PropertyPermissionException(propertyName, node.getPath(), obj.getClass(), "Unable to access field for collection type [" + value.getClass().getName() + "]");
        } else {
            Type type = property.getField().getGenericType();
            if (!(type instanceof ParameterizedType)) {
                throw new PropertyPermissionException(propertyName, node.getPath(), obj.getClass(), "Only collection types with a parameterized type are supported");
            } else {
                ParameterizedType pt = (ParameterizedType)type;
                Type[] pts = pt.getActualTypeArguments();
               
                if (value instanceof Map) {
                    // Maps must have 2 parameterized types
                    if (pts.length != 2) {
                        throw new PropertyPermissionException(propertyName, node.getPath(), obj.getClass(), "Only map types with 2 parameterized types are supported; actual [" + pts.length + "]");
                    }
                    Type keyType = pts[0];
                    Type valueType = pts[1];
                    if (!(keyType instanceof Class && valueType instanceof Class)) {
                        throw new PropertyPermissionException(propertyName, node.getPath(), obj.getClass(), "Only a map with paramertized types of concrete classes are supported (e.g. TreeMap<String,Integer> rather than TreeMap<String,ArrayList<String>>)");
                    }
                    Class keyClass = (Class)keyType;
                    Class valueClass = (Class)valueType;
                    Map m = (Map)value;
                    newch = CollectionHelper.createMapType(m, valuePropertyName, valueClass, keyAttrString, keyClass);
                } else {
                    // Collections must have 1 parameterized type
                    if (pts.length != 1) {
                        throw new PropertyPermissionException(propertyName, node.getPath(), obj.getClass(), "Only collection types with 1 parameterized type are supported; actual [" + pts.length + "]");
                    }
                    Type valueType = pts[0];
                    if (!(valueType instanceof Class)) {
                        throw new PropertyPermissionException(propertyName, node.getPath(), obj.getClass(), "Only a collection with a parameterized type of a concrete class is supported (e.g. ArrayList<String> rather than ArrayList<ArrayList<String>>)");
                    }
                    Class valueClass = (Class)valueType;
                    Collection c = (Collection)value;
                    newch = CollectionHelper.createCollectionType(c, valuePropertyName, valueClass);
                }
            }
        }
       
        return newch;
    }
}
TOP

Related Classes of com.cloudhopper.commons.xbean.XmlBean

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.