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 *      https://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}