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 *      https://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.beanutils;
019
020import java.io.Serializable;
021import java.lang.reflect.InvocationTargetException;
022
023/**
024 * <p>Implements <code>DynaBean</code> to wrap a standard JavaBean
025 * instance, so that DynaBean APIs can be used to access its properties.</p>
026 *
027 * <p>
028 * The most common use cases for this class involve wrapping an existing java bean.
029 * (This makes it different from the typical use cases for other <code>DynaBean</code>'s.)
030 * For example:
031 * </p>
032 * <pre>
033 *  Object aJavaBean = ...;
034 *  ...
035 *  DynaBean db = new WrapDynaBean(aJavaBean);
036 *  ...
037 * </pre>
038 *
039 * <p><strong>IMPLEMENTATION NOTE</strong> - This implementation does not
040 * support the <code>contains()</code> and <code>remove()</code> methods.</p>
041 *
042 */
043public class WrapDynaBean implements DynaBean, Serializable {
044
045    private static final long serialVersionUID = 1L;
046
047    /**
048     * The <code>DynaClass</code> "base class" that this DynaBean
049     * is associated with.
050     */
051    protected transient WrapDynaClass dynaClass;
052
053    /**
054     * The JavaBean instance wrapped by this WrapDynaBean.
055     */
056    protected Object instance;
057
058    /**
059     * Construct a new <code>DynaBean</code> associated with the specified
060     * JavaBean instance.
061     *
062     * @param instance JavaBean instance to be wrapped
063     */
064    public WrapDynaBean(final Object instance) {
065        this(instance, null);
066    }
067
068    /**
069     * Creates a new instance of {@code WrapDynaBean}, associates it with the specified
070     * JavaBean instance, and initializes the bean's {@code DynaClass}. Using this
071     * constructor this {@code WrapDynaBean} instance can be assigned a class which has
072     * been configured externally. If no {@code WrapDynaClass} is provided, a new one is
073     * created using a standard mechanism.
074     *
075     * @param instance JavaBean instance to be wrapped
076     * @param cls the optional {@code WrapDynaClass} to be used for this bean
077     * @since 1.9
078     */
079    public WrapDynaBean(final Object instance, final WrapDynaClass cls) {
080        this.instance = instance;
081        this.dynaClass = cls != null ? cls : (WrapDynaClass) getDynaClass();
082    }
083
084    /**
085     * Does the specified mapped property contain a value for the specified
086     * key value?
087     *
088     * @param name Name of the property to check
089     * @param key Name of the key to check
090     * @return <code>true</code> if the mapped property contains a value for
091     * the specified key, otherwise <code>false</code>
092     *
093     * @throws IllegalArgumentException if there is no property
094     *  of the specified name
095     */
096    @Override
097    public boolean contains(final String name, final String key) {
098        throw new UnsupportedOperationException
099                ("WrapDynaBean does not support contains()");
100    }
101
102    /**
103     * Return the value of a simple property with the specified name.
104     *
105     * @param name Name of the property whose value is to be retrieved
106     * @return The property's value
107     * @throws IllegalArgumentException if there is no property
108     *  of the specified name
109     */
110    @Override
111    public Object get(final String name) {
112        Object value = null;
113        try {
114            value = getPropertyUtils().getSimpleProperty(instance, name);
115        } catch (final InvocationTargetException ite) {
116            final Throwable cause = ite.getTargetException();
117            throw new IllegalArgumentException
118                    ("Error reading property '" + name +
119                              "' nested exception - " + cause);
120        } catch (final Throwable t) {
121            throw new IllegalArgumentException
122                    ("Error reading property '" + name +
123                              "', exception - " + t);
124        }
125        return value;
126    }
127
128    /**
129     * Return the value of an indexed property with the specified name.
130     *
131     * @param name Name of the property whose value is to be retrieved
132     * @param index Index of the value to be retrieved
133     * @return The indexed property's value
134     * @throws IllegalArgumentException if there is no property
135     *  of the specified name
136     * @throws IllegalArgumentException if the specified property
137     *  exists, but is not indexed
138     * @throws IndexOutOfBoundsException if the specified index
139     *  is outside the range of the underlying property
140     * @throws NullPointerException if no array or List has been
141     *  initialized for this property
142     */
143    @Override
144    public Object get(final String name, final int index) {
145
146        Object value = null;
147        try {
148            value = getPropertyUtils().getIndexedProperty(instance, name, index);
149        } catch (final IndexOutOfBoundsException e) {
150            throw e;
151        } catch (final InvocationTargetException ite) {
152            final Throwable cause = ite.getTargetException();
153            throw new IllegalArgumentException
154                    ("Error reading indexed property '" + name +
155                              "' nested exception - " + cause);
156        } catch (final Throwable t) {
157            throw new IllegalArgumentException
158                    ("Error reading indexed property '" + name +
159                              "', exception - " + t);
160        }
161        return value;
162
163    }
164
165    /**
166     * Return the value of a mapped property with the specified name,
167     * or <code>null</code> if there is no value for the specified key.
168     *
169     * @param name Name of the property whose value is to be retrieved
170     * @param key Key of the value to be retrieved
171     * @return The mapped property's value
172     * @throws IllegalArgumentException if there is no property
173     *  of the specified name
174     * @throws IllegalArgumentException if the specified property
175     *  exists, but is not mapped
176     */
177    @Override
178    public Object get(final String name, final String key) {
179
180        Object value = null;
181        try {
182            value = getPropertyUtils().getMappedProperty(instance, name, key);
183        } catch (final InvocationTargetException ite) {
184            final Throwable cause = ite.getTargetException();
185            throw new IllegalArgumentException
186                    ("Error reading mapped property '" + name +
187                              "' nested exception - " + cause);
188        } catch (final Throwable t) {
189            throw new IllegalArgumentException
190                    ("Error reading mapped property '" + name +
191                              "', exception - " + t);
192        }
193        return value;
194
195    }
196
197    /**
198     * Return the <code>DynaClass</code> instance that describes the set of
199     * properties available for this DynaBean.
200     * @return The associated DynaClass
201     */
202    @Override
203    public DynaClass getDynaClass() {
204
205        if (dynaClass == null) {
206            dynaClass = WrapDynaClass.createDynaClass(instance.getClass());
207        }
208
209        return this.dynaClass;
210
211    }
212
213    /**
214     * Return the property descriptor for the specified property name.
215     *
216     * @param name Name of the property for which to retrieve the descriptor
217     * @return The descriptor for the specified property
218     * @throws IllegalArgumentException if this is not a valid property
219     *  name for our DynaClass
220     */
221    protected DynaProperty getDynaProperty(final String name) {
222
223        final DynaProperty descriptor = getDynaClass().getDynaProperty(name);
224        if (descriptor == null) {
225            throw new IllegalArgumentException
226                    ("Invalid property name '" + name + "'");
227        }
228        return descriptor;
229
230    }
231
232    /**
233     * Gets the bean instance wrapped by this DynaBean.
234     * For most common use cases,
235     * this object should already be known
236     * and this method safely be ignored.
237     * But some creators of frameworks using <code>DynaBean</code>'s may
238     * find this useful.
239     *
240     * @return the java bean Object wrapped by this <code>DynaBean</code>
241     */
242    public Object getInstance() {
243        return instance;
244    }
245
246    /**
247     * Returns the {@code PropertyUtilsBean} instance to be used for accessing properties.
248     * If available, this object is obtained from the associated {@code WrapDynaClass}.
249     *
250     * @return the associated {@code PropertyUtilsBean}
251     */
252    private PropertyUtilsBean getPropertyUtils() {
253
254        PropertyUtilsBean propUtils = null;
255        if (dynaClass != null) {
256            propUtils = dynaClass.getPropertyUtilsBean();
257        }
258        return propUtils != null ? propUtils : PropertyUtilsBean.getInstance();
259
260    }
261
262    /**
263     * Remove any existing value for the specified key on the
264     * specified mapped property.
265     *
266     * @param name Name of the property for which a value is to
267     *  be removed
268     * @param key Key of the value to be removed
269     * @throws IllegalArgumentException if there is no property
270     *  of the specified name
271     */
272    @Override
273    public void remove(final String name, final String key) {
274        throw new UnsupportedOperationException
275                ("WrapDynaBean does not support remove()");
276    }
277
278    /**
279     * Set the value of an indexed property with the specified name.
280     *
281     * @param name Name of the property whose value is to be set
282     * @param index Index of the property to be set
283     * @param value Value to which this property is to be set
284     * @throws ConversionException if the specified value cannot be
285     *  converted to the type required for this property
286     * @throws IllegalArgumentException if there is no property
287     *  of the specified name
288     * @throws IllegalArgumentException if the specified property
289     *  exists, but is not indexed
290     * @throws IndexOutOfBoundsException if the specified index
291     *  is outside the range of the underlying property
292     */
293    @Override
294    public void set(final String name, final int index, final Object value) {
295        try {
296            getPropertyUtils().setIndexedProperty(instance, name, index, value);
297        } catch (final IndexOutOfBoundsException e) {
298            throw e;
299        } catch (final InvocationTargetException ite) {
300            final Throwable cause = ite.getTargetException();
301            throw new IllegalArgumentException
302                    ("Error setting indexed property '" + name +
303                              "' nested exception - " + cause);
304        } catch (final Throwable t) {
305            throw new IllegalArgumentException
306                    ("Error setting indexed property '" + name +
307                              "', exception - " + t);
308        }
309    }
310
311    /**
312     * Set the value of a simple property with the specified name.
313     *
314     * @param name Name of the property whose value is to be set
315     * @param value Value to which this property is to be set
316     * @throws ConversionException if the specified value cannot be
317     *  converted to the type required for this property
318     * @throws IllegalArgumentException if there is no property
319     *  of the specified name
320     * @throws NullPointerException if an attempt is made to set a
321     *  primitive property to null
322     */
323    @Override
324    public void set(final String name, final Object value) {
325
326        try {
327            getPropertyUtils().setSimpleProperty(instance, name, value);
328        } catch (final InvocationTargetException ite) {
329            final Throwable cause = ite.getTargetException();
330            throw new IllegalArgumentException
331                    ("Error setting property '" + name +
332                              "' nested exception -" + cause);
333        } catch (final Throwable t) {
334            throw new IllegalArgumentException
335                    ("Error setting property '" + name +
336                              "', exception - " + t);
337        }
338
339    }
340
341    /**
342     * Set the value of a mapped property with the specified name.
343     *
344     * @param name Name of the property whose value is to be set
345     * @param key Key of the property to be set
346     * @param value Value to which this property is to be set
347     * @throws ConversionException if the specified value cannot be
348     *  converted to the type required for this property
349     * @throws IllegalArgumentException if there is no property
350     *  of the specified name
351     * @throws IllegalArgumentException if the specified property
352     *  exists, but is not mapped
353     */
354    @Override
355    public void set(final String name, final String key, final Object value) {
356
357        try {
358            getPropertyUtils().setMappedProperty(instance, name, key, value);
359        } catch (final InvocationTargetException ite) {
360            final Throwable cause = ite.getTargetException();
361            throw new IllegalArgumentException
362                    ("Error setting mapped property '" + name +
363                              "' nested exception - " + cause);
364        } catch (final Throwable t) {
365            throw new IllegalArgumentException
366                    ("Error setting mapped property '" + name +
367                              "', exception - " + t);
368        }
369
370    }
371}