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.beanutils2; 019 020import java.lang.reflect.Constructor; 021import java.lang.reflect.InvocationTargetException; 022import java.util.HashMap; 023import java.util.Objects; 024 025/** 026 * <p> 027 * Minimal implementation of the {@code DynaClass} interface. Can be used as a convenience base class for more sophisticated implementations. 028 * </p> 029 * <p> 030 * <strong>IMPLEMENTATION NOTE</strong> - The {@code DynaBean} implementation class supplied to our constructor MUST have a one-argument constructor of its own 031 * that accepts a {@code DynaClass}. This is used to associate the DynaBean instance with this DynaClass. 032 * </p> 033 */ 034public class BasicDynaClass implements DynaClass { 035 036 private static final long serialVersionUID = 1L; 037 038 /** 039 * The method signature of the constructor we will use to create new DynaBean instances. 040 */ 041 private static final Class<?>[] CONSTRUCTOR_TYPES = { DynaClass.class }; 042 043 /** 044 * The constructor of the {@code dynaBeanClass} that we will use for creating new instances. 045 */ 046 protected transient Constructor<?> constructor; 047 048 /** 049 * The argument values to be passed to the constructor we will use to create new DynaBean instances. 050 */ 051 protected Object[] constructorValues = { this }; 052 053 /** 054 * The {@code DynaBean} implementation class we will use for creating new instances. 055 */ 056 protected Class<?> dynaBeanClass = BasicDynaBean.class; 057 058 /** 059 * The "name" of this DynaBean class. 060 */ 061 protected String name = this.getClass().getName(); 062 063 /** 064 * The set of dynamic properties that are part of this DynaClass. 065 */ 066 protected DynaProperty[] properties = DynaProperty.EMPTY_ARRAY; 067 068 /** 069 * 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 070 * those in the {@code properties} list. 071 */ 072 protected HashMap<String, DynaProperty> propertiesMap = new HashMap<>(); 073 074 /** 075 * Constructs a new BasicDynaClass with default parameters. 076 */ 077 public BasicDynaClass() { 078 this(null, null, null); 079 } 080 081 /** 082 * Constructs a new BasicDynaClass with the specified parameters. 083 * 084 * @param name Name of this DynaBean class 085 * @param dynaBeanClass The implementation class for new instances 086 */ 087 public BasicDynaClass(final String name, final Class<?> dynaBeanClass) { 088 this(name, dynaBeanClass, null); 089 } 090 091 /** 092 * Constructs a new BasicDynaClass with the specified parameters. 093 * 094 * @param name Name of this DynaBean class 095 * @param dynaBeanClass The implementation class for new instances 096 * @param properties Property descriptors for the supported properties 097 */ 098 public BasicDynaClass(final String name, Class<?> dynaBeanClass, final DynaProperty[] properties) { 099 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}