    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.beanutils;
    019
    020import java.io.Serializable;
    021import java.lang.reflect.Constructor;
    022import java.lang.reflect.InvocationTargetException;
    023import java.util.HashMap;
    024
    025/**
    026 * <p>Minimal implementation of the <code>DynaClass</code> interface.  Can be
    027 * used as a convenience base class for more sophisticated implementations.</p> *
    028 * <p><strong>IMPLEMENTATION NOTE</strong> - The <code>DynaBean</code>
    029 * implementation class supplied to our constructor MUST have a one-argument
    030 * constructor of its own that accepts a <code>DynaClass</code>.  This is
    031 * used to associate the DynaBean instance with this DynaClass.</p>
    032 *
    033 */
    034public class BasicDynaClass implements DynaClass, Serializable {
    035
    036    private static final long serialVersionUID = 1L;
    037
    038    /**
    039     * The method signature of the constructor we will use to create
    040     * new DynaBean instances.
    041     */
    042    protected static Class<?>[] constructorTypes = { DynaClass.class };
    043
    044    /**
    045     * The constructor of the <code>dynaBeanClass</code> that we will use
    046     * for creating new instances.
    047     */
    048    protected transient Constructor<?> constructor;
    049
    050    /**
    051     * The argument values to be passed to the constructore we will use
    052     * to create new DynaBean instances.
    053     */
    054    protected Object[] constructorValues = { this };
    055
    056    /**
    057     * The <code>DynaBean</code> implementation class we will use for
    058     * creating new instances.
    059     */
    060    protected Class<?> dynaBeanClass = BasicDynaBean.class;
    061
    062    /**
    063     * The "name" of this DynaBean class.
    064     */
    065    protected String name = this.getClass().getName();
    066
    067    /**
    068     * The set of dynamic properties that are part of this DynaClass.
    069     */
    070    protected DynaProperty[] properties = {};
    071
    072    /**
    073     * The set of dynamic properties that are part of this DynaClass,
    074     * keyed by the property name.  Individual descriptor instances will
    075     * be the same instances as those in the <code>properties</code> list.
    076     */
    077    protected HashMap<String, DynaProperty> propertiesMap = new HashMap<>();
    078
    079    /**
    080     * Construct a new BasicDynaClass with default parameters.
    081     */
    082    public BasicDynaClass() {
    083        this(null, null, null);
    084    }
    085
    086    /**
    087     * Construct a new BasicDynaClass with the specified parameters.
    088     *
    089     * @param name Name of this DynaBean class
    090     * @param dynaBeanClass The implementation class for new instances
    091     */
    092    public BasicDynaClass(final String name, final Class<?> dynaBeanClass) {
    093        this(name, dynaBeanClass, null);
    094    }
    095
    096    /**
    097     * Construct a new BasicDynaClass with the specified parameters.
    098     *
    099     * @param name Name of this DynaBean class
    100     * @param dynaBeanClass The implementation class for new intances
    101     * @param properties Property descriptors for the supported properties
    102     */
    103    public BasicDynaClass(final String name, Class<?> dynaBeanClass,
    104                          final DynaProperty[] properties) {
    105        if (name != null) {
    106            this.name = name;
    107        }
    108        if (dynaBeanClass == null) {
    109            dynaBeanClass = BasicDynaBean.class;
    110        }
    111        setDynaBeanClass(dynaBeanClass);
    112        if (properties != null) {
    113            setProperties(properties);
    114        }
    115    }
    116
    117    /**
    118     * Return the Class object we will use to create new instances in the
    119     * <code>newInstance()</code> method.  This Class <strong>MUST</strong>
    120     * implement the <code>DynaBean</code> interface.
    121     *
    122     * @return The class of the {@link DynaBean}
    123     */
    124    public Class<?> getDynaBeanClass() {
    125        return this.dynaBeanClass;
    126    }
    127
    128    /**
    129     * <p>Return an array of <code>ProperyDescriptors</code> for the properties
    130     * currently defined in this DynaClass.  If no properties are defined, a
    131     * zero-length array will be returned.</p>
    132     *
    133     * <p><strong>FIXME</strong> - Should we really be implementing
    134     * <code>getBeanInfo()</code> instead, which returns property descriptors
    135     * and a bunch of other stuff?</p>
    136     *
    137     * @return the set of properties for this DynaClass
    138     */
    139    @Override
    140    public DynaProperty[] getDynaProperties() {
    141        return properties;
    142    }
    143
    144    /**
    145     * Return a property descriptor for the specified property, if it exists;
    146     * otherwise, return <code>null</code>.
    147     *
    148     * @param name Name of the dynamic property for which a descriptor
    149     *  is requested
    150     * @return The descriptor for the specified property
    151     * @throws IllegalArgumentException if no property name is specified
    152     */
    153    @Override
    154    public DynaProperty getDynaProperty(final String name) {
    155        if (name == null) {
    156            throw new IllegalArgumentException
    157                    ("No property name specified");
    158        }
    159        return propertiesMap.get(name);
    160    }
    161
    162    /**
    163     * Return the name of this DynaClass (analogous to the
    164     * <code>getName()</code> method of <code>java.lang.Class</code>), which
    165     * allows the same <code>DynaClass</code> implementation class to support
    166     * different dynamic classes, with different sets of properties.
    167     *
    168     * @return the name of the DynaClass
    169     */
    170    @Override
    171    public String getName() {
    172        return this.name;
    173    }
    174
    175    /**
    176     * Instantiate and return a new DynaBean instance, associated
    177     * with this DynaClass.
    178     *
    179     * @return A new <code>DynaBean</code> instance
    180     * @throws IllegalAccessException if the Class or the appropriate
    181     *  constructor is not accessible
    182     * @throws InstantiationException if this Class represents an abstract
    183     *  class, an array class, a primitive type, or void; or if instantiation
    184     *  fails for some other reason
    185     */
    186    @Override
    187    public DynaBean newInstance()
    188            throws IllegalAccessException, InstantiationException {
    189        try {
    190            // Refind the constructor after a deserialization (if needed)
    191            if (constructor == null) {
    192                setDynaBeanClass(this.dynaBeanClass);
    193            }
    194            // Invoke the constructor to create a new bean instance
    195            return (DynaBean) constructor.newInstance(constructorValues);
    196        } catch (final InvocationTargetException e) {
    197            throw new InstantiationException
    198                    (e.getTargetException().getMessage());
    199        }
    200    }
    201
    202    /**
    203     * Set the Class object we will use to create new instances in the
    204     * <code>newInstance()</code> method.  This Class <strong>MUST</strong>
    205     * implement the <code>DynaBean</code> interface.
    206     *
    207     * @param dynaBeanClass The new Class object
    208     * @throws IllegalArgumentException if the specified Class does not
    209     *  implement the <code>DynaBean</code> interface
    210     */
    211    protected void setDynaBeanClass(final Class<?> dynaBeanClass) {
    212        // Validate the argument type specified
    213        if (dynaBeanClass.isInterface()) {
    214            throw new IllegalArgumentException
    215                    ("Class " + dynaBeanClass.getName() +
    216                    " is an interface, not a class");
    217        }
    218        if (!DynaBean.class.isAssignableFrom(dynaBeanClass)) {
    219            throw new IllegalArgumentException
    220                    ("Class " + dynaBeanClass.getName() +
    221                    " does not implement DynaBean");
    222        }
    223        // Identify the Constructor we will use in newInstance()
    224        try {
    225            this.constructor = dynaBeanClass.getConstructor(constructorTypes);
    226        } catch (final NoSuchMethodException e) {
    227            throw new IllegalArgumentException
    228                    ("Class " + dynaBeanClass.getName() +
    229                    " does not have an appropriate constructor");
    230        }
    231        this.dynaBeanClass = dynaBeanClass;
    232    }
    233
    234    /**
    235     * Set the list of dynamic properties supported by this DynaClass.
    236     *
    237     * @param properties List of dynamic properties to be supported
    238     */
    239    protected void setProperties(final DynaProperty[] properties) {
    240        this.properties = properties;
    241        propertiesMap.clear();
    242        for (final DynaProperty propertie : properties) {
    243            propertiesMap.put(propertie.getName(), propertie);
    244        }
    245    }
    246
    247}