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 * https://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.InvocationTargetException;
21
22 /**
23 * <p>
24 * Implements {@code DynaBean} to wrap a standard JavaBean instance, so that DynaBean APIs can be used to access its properties.
25 * </p>
26 *
27 * <p>
28 * The most common use cases for this class involve wrapping an existing Java bean. (This makes it different from the typical use cases for other
29 * {@code DynaBean}'s.) For example:
30 * </p>
31 *
32 * <pre>{@code
33 * Object aJavaBean = ...;
34 * ...
35 * DynaBean db = new WrapDynaBean(aJavaBean);
36 * ...
37 * }</pre>
38 *
39 * <p>
40 * <strong>IMPLEMENTATION NOTE</strong> - This implementation does not support the {@code contains()</code> and <code>remove()} methods.
41 * </p>
42 */
43
44 public class WrapDynaBean implements DynaBean {
45
46 private static final long serialVersionUID = 1L;
47
48 /**
49 * The {@code DynaClass} "base class" that this DynaBean is associated with.
50 */
51 protected transient WrapDynaClass dynaClass;
52
53 /**
54 * The JavaBean instance wrapped by this WrapDynaBean.
55 */
56 protected Object instance;
57
58 /**
59 * Constructs a new {@code DynaBean} associated with the specified JavaBean instance.
60 *
61 * @param instance JavaBean instance to be wrapped
62 */
63 public WrapDynaBean(final Object instance) {
64 this(instance, null);
65 }
66
67 /**
68 * Creates a new instance of {@code WrapDynaBean}, associates it with the specified JavaBean instance, and initializes the bean's {@code DynaClass}. Using
69 * this constructor this {@code WrapDynaBean} instance can be assigned a class which has been configured externally. If no {@code WrapDynaClass} is
70 * provided, a new one is created using a standard mechanism.
71 *
72 * @param instance JavaBean instance to be wrapped
73 * @param cls the optional {@code WrapDynaClass} to be used for this bean
74 * @since 1.9
75 */
76 public WrapDynaBean(final Object instance, final WrapDynaClass cls) {
77 this.instance = instance;
78 this.dynaClass = cls != null ? cls : (WrapDynaClass) getDynaClass();
79 }
80
81 /**
82 * Does the specified mapped property contain a value for the specified key value?
83 *
84 * @param name Name of the property to check
85 * @param key Name of the key to check
86 * @return {@code true} if the mapped property contains a value for the specified key, otherwise {@code false}
87 * @throws IllegalArgumentException if there is no property of the specified name
88 */
89 @Override
90 public boolean contains(final String name, final String key) {
91 throw new UnsupportedOperationException("WrapDynaBean does not support contains()");
92 }
93
94 /**
95 * Gets the value of a simple property with the specified name.
96 *
97 * @param name Name of the property whose value is to be retrieved
98 * @return The property's value
99 * @throws IllegalArgumentException if there is no property of the specified name
100 * @throws NullPointerException for null input.
101 */
102 @Override
103 public Object get(final String name) {
104 Object value = null;
105 try {
106 value = getPropertyUtils().getSimpleProperty(instance, name);
107 } catch (final InvocationTargetException ite) {
108 final Throwable cause = ite.getTargetException();
109 throw new IllegalArgumentException("Error reading property '" + name + "' nested exception - " + cause);
110 } catch (final NullPointerException t) {
111 throw t;
112 } catch (final Throwable t) {
113 throw new IllegalArgumentException("Error reading property '" + name + "', exception - " + t);
114 }
115 return value;
116 }
117
118 /**
119 * Gets the value of an indexed property with the specified name.
120 *
121 * @param name Name of the property whose value is to be retrieved
122 * @param index Index of the value to be retrieved
123 * @return The indexed property's value
124 * @throws IllegalArgumentException if there is no property of the specified name
125 * @throws IllegalArgumentException if the specified property exists, but is not indexed
126 * @throws IndexOutOfBoundsException if the specified index is outside the range of the underlying property
127 * @throws NullPointerException if no array or List has been initialized for this property
128 */
129 @Override
130 public Object get(final String name, final int index) {
131 Object value = null;
132 try {
133 value = getPropertyUtils().getIndexedProperty(instance, name, index);
134 } catch (final IndexOutOfBoundsException e) {
135 throw e;
136 } catch (final InvocationTargetException ite) {
137 final Throwable cause = ite.getTargetException();
138 throw new IllegalArgumentException("Error reading indexed property '" + name + "' nested exception - " + cause);
139 } catch (final Throwable t) {
140 throw new IllegalArgumentException("Error reading indexed property '" + name + "', exception - " + t);
141 }
142 return value;
143 }
144
145 /**
146 * Gets the value of a mapped property with the specified name, or {@code null} if there is no value for the specified key.
147 *
148 * @param name Name of the property whose value is to be retrieved
149 * @param key Key of the value to be retrieved
150 * @return The mapped property's value
151 * @throws IllegalArgumentException if there is no property of the specified name
152 * @throws IllegalArgumentException if the specified property exists, but is not mapped
153 */
154 @Override
155 public Object get(final String name, final String key) {
156 Object value = null;
157 try {
158 value = getPropertyUtils().getMappedProperty(instance, name, key);
159 } catch (final InvocationTargetException ite) {
160 final Throwable cause = ite.getTargetException();
161 throw new IllegalArgumentException("Error reading mapped property '" + name + "' nested exception - " + cause);
162 } catch (final Throwable t) {
163 throw new IllegalArgumentException("Error reading mapped property '" + name + "', exception - " + t);
164 }
165 return value;
166 }
167
168 /**
169 * Gets the {@code DynaClass} instance that describes the set of properties available for this DynaBean.
170 *
171 * @return The associated DynaClass
172 */
173 @Override
174 public DynaClass getDynaClass() {
175 if (dynaClass == null) {
176 dynaClass = WrapDynaClass.createDynaClass(instance.getClass());
177 }
178
179 return this.dynaClass;
180 }
181
182 /**
183 * Gets the property descriptor for the specified property name.
184 *
185 * @param name Name of the property for which to retrieve the descriptor
186 * @return The descriptor for the specified property
187 * @throws IllegalArgumentException if this is not a valid property name for our DynaClass
188 */
189 protected DynaProperty getDynaProperty(final String name) {
190 final DynaProperty descriptor = getDynaClass().getDynaProperty(name);
191 if (descriptor == null) {
192 throw new IllegalArgumentException("Invalid property name '" + name + "'");
193 }
194 return descriptor;
195 }
196
197 /**
198 * Gets the bean instance wrapped by this DynaBean. For most common use cases, this object should already be known and this method safely be ignored. But
199 * some creators of frameworks using {@code DynaBean}'s may find this useful.
200 *
201 * @return the Java bean Object wrapped by this {@code DynaBean}
202 */
203 public Object getInstance() {
204 return instance;
205 }
206
207 /**
208 * Returns the {@code PropertyUtilsBean} instance to be used for accessing properties. If available, this object is obtained from the associated
209 * {@code WrapDynaClass}.
210 *
211 * @return the associated {@code PropertyUtilsBean}
212 */
213 private PropertyUtilsBean getPropertyUtils() {
214 PropertyUtilsBean propUtils = null;
215 if (dynaClass != null) {
216 propUtils = dynaClass.getPropertyUtilsBean();
217 }
218 return propUtils != null ? propUtils : PropertyUtilsBean.getInstance();
219 }
220
221 /**
222 * Remove any existing value for the specified key on the specified mapped property.
223 *
224 * @param name Name of the property for which a value is to be removed
225 * @param key Key of the value to be removed
226 * @throws IllegalArgumentException if there is no property of the specified name
227 */
228 @Override
229 public void remove(final String name, final String key) {
230 throw new UnsupportedOperationException("WrapDynaBean does not support remove()");
231 }
232
233 /**
234 * Sets the value of an indexed property with the specified name.
235 *
236 * @param name Name of the property whose value is to be set
237 * @param index Index of the property to be set
238 * @param value Value to which this property is to be set
239 * @throws ConversionException if the specified value cannot be converted to the type required for this property
240 * @throws IllegalArgumentException if there is no property of the specified name
241 * @throws IllegalArgumentException if the specified property exists, but is not indexed
242 * @throws IndexOutOfBoundsException if the specified index is outside the range of the underlying property
243 */
244 @Override
245 public void set(final String name, final int index, final Object value) {
246 try {
247 getPropertyUtils().setIndexedProperty(instance, name, index, value);
248 } catch (final IndexOutOfBoundsException e) {
249 throw e;
250 } catch (final InvocationTargetException ite) {
251 final Throwable cause = ite.getTargetException();
252 throw new IllegalArgumentException("Error setting indexed property '" + name + "' nested exception - " + cause);
253 } catch (final Throwable t) {
254 throw new IllegalArgumentException("Error setting indexed property '" + name + "', exception - " + t);
255 }
256 }
257
258 /**
259 * Sets the value of a simple property with the specified name.
260 *
261 * @param name Name of the property whose value is to be set
262 * @param value Value to which this property is to be set
263 * @throws ConversionException if the specified value cannot be converted to the type required for this property
264 * @throws IllegalArgumentException if there is no property of the specified name
265 * @throws NullPointerException if an attempt is made to set a primitive property to null
266 */
267 @Override
268 public void set(final String name, final Object value) {
269 try {
270 getPropertyUtils().setSimpleProperty(instance, name, value);
271 } catch (final InvocationTargetException ite) {
272 final Throwable cause = ite.getTargetException();
273 throw new IllegalArgumentException("Error setting property '" + name + "' nested exception -" + cause);
274 } catch (final Throwable t) {
275 throw new IllegalArgumentException("Error setting property '" + name + "', exception - " + t);
276 }
277 }
278
279 /**
280 * Sets the value of a mapped property with the specified name.
281 *
282 * @param name Name of the property whose value is to be set
283 * @param key Key of the property to be set
284 * @param value Value to which this property is to be set
285 * @throws ConversionException if the specified value cannot be converted to the type required for this property
286 * @throws IllegalArgumentException if there is no property of the specified name
287 * @throws IllegalArgumentException if the specified property exists, but is not mapped
288 */
289 @Override
290 public void set(final String name, final String key, final Object value) {
291 try {
292 getPropertyUtils().setMappedProperty(instance, name, key, value);
293 } catch (final InvocationTargetException ite) {
294 final Throwable cause = ite.getTargetException();
295 throw new IllegalArgumentException("Error setting mapped property '" + name + "' nested exception - " + cause);
296 } catch (final Throwable t) {
297 throw new IllegalArgumentException("Error setting mapped property '" + name + "', exception - " + t);
298 }
299 }
300 }