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
018
019package org.apache.commons.beanutils;
020
021
022import java.io.Serializable;
023import java.lang.reflect.Constructor;
024import java.lang.reflect.InvocationTargetException;
025import java.util.HashMap;
026
027
028/**
029 * <p>Minimal implementation of the <code>DynaClass</code> interface.  Can be
030 * used as a convenience base class for more sophisticated implementations.</p>
031 * <p><strong>IMPLEMENTATION NOTE</strong> - The <code>DynaBean</code>
032 * implementation class supplied to our constructor MUST have a one-argument
033 * constructor of its own that accepts a <code>DynaClass</code>.  This is
034 * used to associate the DynaBean instance with this DynaClass.</p>
035 *
036 * @version $Id$
037 */
038
039public class BasicDynaClass implements DynaClass, Serializable {
040
041
042    // ----------------------------------------------------------- Constructors
043
044
045    /**
046     * Construct a new BasicDynaClass with default parameters.
047     */
048    public BasicDynaClass() {
049
050        this(null, null, null);
051
052    }
053
054
055    /**
056     * Construct a new BasicDynaClass with the specified parameters.
057     *
058     * @param name Name of this DynaBean class
059     * @param dynaBeanClass The implementation class for new instances
060     */
061    public BasicDynaClass(final String name, final Class<?> dynaBeanClass) {
062
063        this(name, dynaBeanClass, null);
064
065    }
066
067
068    /**
069     * Construct a new BasicDynaClass with the specified parameters.
070     *
071     * @param name Name of this DynaBean class
072     * @param dynaBeanClass The implementation class for new intances
073     * @param properties Property descriptors for the supported properties
074     */
075    public BasicDynaClass(final String name, Class<?> dynaBeanClass,
076                          final DynaProperty[] properties) {
077
078        super();
079        if (name != null) {
080            this.name = name;
081        }
082        if (dynaBeanClass == null) {
083            dynaBeanClass = BasicDynaBean.class;
084        }
085        setDynaBeanClass(dynaBeanClass);
086        if (properties != null) {
087            setProperties(properties);
088        }
089
090    }
091
092
093    // ----------------------------------------------------- Instance Variables
094
095
096    /**
097     * The constructor of the <code>dynaBeanClass</code> that we will use
098     * for creating new instances.
099     */
100    protected transient Constructor<?> constructor = null;
101
102
103    /**
104     * The method signature of the constructor we will use to create
105     * new DynaBean instances.
106     */
107    protected static Class<?>[] constructorTypes = { DynaClass.class };
108
109
110    /**
111     * The argument values to be passed to the constructore we will use
112     * to create new DynaBean instances.
113     */
114    protected Object[] constructorValues = { this };
115
116
117    /**
118     * The <code>DynaBean</code> implementation class we will use for
119     * creating new instances.
120     */
121    protected Class<?> dynaBeanClass = BasicDynaBean.class;
122
123
124    /**
125     * The "name" of this DynaBean class.
126     */
127    protected String name = this.getClass().getName();
128
129
130    /**
131     * The set of dynamic properties that are part of this DynaClass.
132     */
133    protected DynaProperty[] properties = new DynaProperty[0];
134
135
136    /**
137     * The set of dynamic properties that are part of this DynaClass,
138     * keyed by the property name.  Individual descriptor instances will
139     * be the same instances as those in the <code>properties</code> list.
140     */
141    protected HashMap<String, DynaProperty> propertiesMap = new HashMap<String, DynaProperty>();
142
143
144    // ------------------------------------------------------ DynaClass Methods
145
146
147    /**
148     * Return the name of this DynaClass (analogous to the
149     * {@code getName()} method of {@code java.lang.Class}, which
150     * allows the same {@code DynaClass} implementation class to support
151     * different dynamic classes, with different sets of properties.
152     *
153     * @return the name of the DynaClass
154     */
155    public String getName() {
156
157        return (this.name);
158
159    }
160
161
162    /**
163     * Return a property descriptor for the specified property, if it exists;
164     * otherwise, return {@code null}.
165     *
166     * @param name Name of the dynamic property for which a descriptor
167     *  is requested
168     * @return The descriptor for the specified property
169     *
170     * @throws IllegalArgumentException if no property name is specified
171     */
172    public DynaProperty getDynaProperty(final String name) {
173
174        if (name == null) {
175            throw new IllegalArgumentException
176                    ("No property name specified");
177        }
178        return propertiesMap.get(name);
179
180    }
181
182
183    /**
184     * <p>Return an array of {@code ProperyDescriptors} for the properties
185     * currently defined in this DynaClass.  If no properties are defined, a
186     * zero-length array will be returned.</p>
187     *
188     * <p><strong>FIXME</strong> - Should we really be implementing
189     * {@code getBeanInfo()} instead, which returns property descriptors
190     * and a bunch of other stuff?</p>
191     *
192     * @return the set of properties for this DynaClass
193     */
194    public DynaProperty[] getDynaProperties() {
195
196        return (properties);
197
198    }
199
200
201    /**
202     * Instantiate and return a new DynaBean instance, associated
203     * with this DynaClass.
204     *
205     * @return A new {@code DynaBean} instance
206     * @throws IllegalAccessException if the Class or the appropriate
207     *  constructor is not accessible
208     * @throws InstantiationException if this Class represents an abstract
209     *  class, an array class, a primitive type, or void; or if instantiation
210     *  fails for some other reason
211     */
212    public DynaBean newInstance()
213            throws IllegalAccessException, InstantiationException {
214
215        try {
216            // Refind the constructor after a deserialization (if needed)
217            if (constructor == null) {
218                setDynaBeanClass(this.dynaBeanClass);
219            }
220            // Invoke the constructor to create a new bean instance
221            return ((DynaBean) constructor.newInstance(constructorValues));
222        } catch (final InvocationTargetException e) {
223            throw new InstantiationException
224                    (e.getTargetException().getMessage());
225        }
226
227    }
228
229
230    // --------------------------------------------------------- Public Methods
231
232
233    /**
234     * Return the Class object we will use to create new instances in the
235     * {@code newInstance()} method.  This Class <strong>MUST</strong>
236     * implement the {@code DynaBean} interface.
237     *
238     * @return The class of the {@link DynaBean}
239     */
240    public Class<?> getDynaBeanClass() {
241
242        return (this.dynaBeanClass);
243
244    }
245
246
247    // ------------------------------------------------------ Protected Methods
248
249
250    /**
251     * Set the Class object we will use to create new instances in the
252     * {@code newInstance()} method.  This Class <strong>MUST</strong>
253     * implement the {@code DynaBean} interface.
254     *
255     * @param dynaBeanClass The new Class object
256     *
257     * @throws IllegalArgumentException if the specified Class does not
258     *  implement the {@code DynaBean} interface
259     */
260    protected void setDynaBeanClass(final Class<?> dynaBeanClass) {
261
262        // Validate the argument type specified
263        if (dynaBeanClass.isInterface()) {
264            throw new IllegalArgumentException
265                    ("Class " + dynaBeanClass.getName() +
266                    " is an interface, not a class");
267        }
268        if (!DynaBean.class.isAssignableFrom(dynaBeanClass)) {
269            throw new IllegalArgumentException
270                    ("Class " + dynaBeanClass.getName() +
271                    " does not implement DynaBean");
272        }
273
274        // Identify the Constructor we will use in newInstance()
275        try {
276            this.constructor = dynaBeanClass.getConstructor(constructorTypes);
277        } catch (final NoSuchMethodException e) {
278            throw new IllegalArgumentException
279                    ("Class " + dynaBeanClass.getName() +
280                    " does not have an appropriate constructor");
281        }
282        this.dynaBeanClass = dynaBeanClass;
283
284    }
285
286
287    /**
288     * Set the list of dynamic properties supported by this DynaClass.
289     *
290     * @param properties List of dynamic properties to be supported
291     */
292    protected void setProperties(final DynaProperty[] properties) {
293
294        this.properties = properties;
295        propertiesMap.clear();
296        for (DynaProperty propertie : properties) {
297            propertiesMap.put(propertie.getName(), propertie);
298        }
299
300    }
301
302
303}