View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.beanutils2;
19  
20  import java.lang.reflect.Constructor;
21  import java.lang.reflect.InvocationTargetException;
22  import java.util.HashMap;
23  import java.util.Objects;
24  
25  /**
26   * <p>
27   * Minimal implementation of the {@code DynaClass} interface. Can be used as a convenience base class for more sophisticated implementations.
28   * </p>
29   * <p>
30   * <strong>IMPLEMENTATION NOTE</strong> - The {@code DynaBean} implementation class supplied to our constructor MUST have a one-argument constructor of its own
31   * that accepts a {@code DynaClass}. This is used to associate the DynaBean instance with this DynaClass.
32   * </p>
33   */
34  public class BasicDynaClass implements DynaClass {
35  
36      private static final long serialVersionUID = 1L;
37  
38      /**
39       * The method signature of the constructor we will use to create new DynaBean instances.
40       */
41      private static final Class<?>[] CONSTRUCTOR_TYPES = { DynaClass.class };
42  
43      /**
44       * The constructor of the {@code dynaBeanClass} that we will use for creating new instances.
45       */
46      protected transient Constructor<?> constructor;
47  
48      /**
49       * The argument values to be passed to the constructor we will use to create new DynaBean instances.
50       */
51      protected Object[] constructorValues = { this };
52  
53      /**
54       * The {@code DynaBean} implementation class we will use for creating new instances.
55       */
56      protected Class<?> dynaBeanClass = BasicDynaBean.class;
57  
58      /**
59       * The "name" of this DynaBean class.
60       */
61      protected String name = this.getClass().getName();
62  
63      /**
64       * The set of dynamic properties that are part of this DynaClass.
65       */
66      protected DynaProperty[] properties = DynaProperty.EMPTY_ARRAY;
67  
68      /**
69       * The set of dynamic properties that are part of this DynaClass, keyed by the property name. Individual descriptor instances will be the same instances as
70       * those in the {@code properties} list.
71       */
72      protected HashMap<String, DynaProperty> propertiesMap = new HashMap<>();
73  
74      /**
75       * Constructs a new BasicDynaClass with default parameters.
76       */
77      public BasicDynaClass() {
78          this(null, null, null);
79      }
80  
81      /**
82       * Constructs a new BasicDynaClass with the specified parameters.
83       *
84       * @param name          Name of this DynaBean class
85       * @param dynaBeanClass The implementation class for new instances
86       */
87      public BasicDynaClass(final String name, final Class<?> dynaBeanClass) {
88          this(name, dynaBeanClass, null);
89      }
90  
91      /**
92       * Constructs a new BasicDynaClass with the specified parameters.
93       *
94       * @param name          Name of this DynaBean class
95       * @param dynaBeanClass The implementation class for new instances
96       * @param properties    Property descriptors for the supported properties
97       */
98      public BasicDynaClass(final String name, Class<?> dynaBeanClass, final DynaProperty[] properties) {
99          if (name != null) {
100             this.name = name;
101         }
102         if (dynaBeanClass == null) {
103             dynaBeanClass = BasicDynaBean.class;
104         }
105         setDynaBeanClass(dynaBeanClass);
106         if (properties != null) {
107             setProperties(properties);
108         }
109     }
110 
111     /**
112      * Gets the Class object we will use to create new instances in the {@code newInstance()} method. This Class <strong>MUST</strong> implement the
113      * {@code DynaBean} interface.
114      *
115      * @return The class of the {@link DynaBean}
116      */
117     public Class<?> getDynaBeanClass() {
118         return this.dynaBeanClass;
119     }
120 
121     /**
122      * <p>
123      * Return an array of {@code PropertyDescriptor} for the properties currently defined in this DynaClass. If no properties are defined, a zero-length array
124      * will be returned.
125      * </p>
126      *
127      * <p>
128      * <strong>FIXME</strong> - Should we really be implementing {@code getBeanInfo()} instead, which returns property descriptors and a bunch of other stuff?
129      * </p>
130      *
131      * @return the set of properties for this DynaClass
132      */
133     @Override
134     public DynaProperty[] getDynaProperties() {
135         return properties.clone();
136     }
137 
138     /**
139      * Gets a property descriptor for the specified property, if it exists; otherwise, return {@code null}.
140      *
141      * @param name Name of the dynamic property for which a descriptor is requested
142      * @return The descriptor for the specified property
143      * @throws IllegalArgumentException if no property name is specified
144      */
145     @Override
146     public DynaProperty getDynaProperty(final String name) {
147         return propertiesMap.get(Objects.requireNonNull(name, "name"));
148     }
149 
150     /**
151      * Gets the name of this DynaClass (analogous to the {@code getName()} method of {@link Class}, which allows the same {@code DynaClass} implementation class
152      * to support different dynamic classes, with different sets of properties.
153      *
154      * @return the name of the DynaClass
155      */
156     @Override
157     public String getName() {
158         return this.name;
159     }
160 
161     /**
162      * Instantiate and return a new DynaBean instance, associated with this DynaClass.
163      *
164      * @return A new {@code DynaBean} instance
165      * @throws IllegalAccessException if the Class or the appropriate constructor is not accessible
166      * @throws InstantiationException if this Class represents an abstract class, an array class, a primitive type, or void; or if instantiation fails for some
167      *                                other reason
168      */
169     @Override
170     public DynaBean newInstance() throws IllegalAccessException, InstantiationException {
171         try {
172             // Refind the constructor after a deserialization (if needed)
173             if (constructor == null) {
174                 setDynaBeanClass(this.dynaBeanClass);
175             }
176             // Invoke the constructor to create a new bean instance
177             return (DynaBean) constructor.newInstance(constructorValues);
178         } catch (final InvocationTargetException e) {
179             throw new InstantiationException(e.getTargetException().getMessage());
180         }
181     }
182 
183     /**
184      * Sets the Class object we will use to create new instances in the {@code newInstance()} method. This Class <strong>MUST</strong> implement the
185      * {@code DynaBean} interface.
186      *
187      * @param dynaBeanClass The new Class object
188      * @throws IllegalArgumentException if the specified Class does not implement the {@code DynaBean} interface
189      */
190     protected void setDynaBeanClass(final Class<?> dynaBeanClass) {
191         // Validate the argument type specified
192         if (dynaBeanClass.isInterface()) {
193             throw new IllegalArgumentException("Class " + dynaBeanClass.getName() + " is an interface, not a class");
194         }
195         if (!DynaBean.class.isAssignableFrom(dynaBeanClass)) {
196             throw new IllegalArgumentException("Class " + dynaBeanClass.getName() + " does not implement DynaBean");
197         }
198 
199         // Identify the Constructor we will use in newInstance()
200         try {
201             this.constructor = dynaBeanClass.getConstructor(CONSTRUCTOR_TYPES);
202         } catch (final NoSuchMethodException e) {
203             throw new IllegalArgumentException("Class " + dynaBeanClass.getName() + " does not have an appropriate constructor");
204         }
205         this.dynaBeanClass = dynaBeanClass;
206     }
207 
208     /**
209      * Sets the list of dynamic properties supported by this DynaClass.
210      *
211      * @param properties List of dynamic properties to be supported
212      */
213     protected void setProperties(final DynaProperty[] properties) {
214         this.properties = Objects.requireNonNull(properties, "properties");
215         propertiesMap.clear();
216         for (final DynaProperty property : properties) {
217             propertiesMap.put(property.getName(), property);
218         }
219     }
220 
221 }