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 }