    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.beanutils2;
    019
    020import java.lang.reflect.InvocationTargetException;
    021
    022/**
    023 * <p>
    024 * Implements {@code DynaBean} to wrap a standard JavaBean instance, so that DynaBean APIs can be used to access its properties.
    025 * </p>
    026 *
    027 * <p>
    028 * 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
    029 * {@code DynaBean}'s.) For example:
    030 * </p>
    031 *
    032 * <pre>{@code
    033 *  Object aJavaBean = ...;
    034 *  ...
    035 *  DynaBean db = new WrapDynaBean(aJavaBean);
    036 *  ...
    037 * }</pre>
    038 *
    039 * <p>
    040 * <strong>IMPLEMENTATION NOTE</strong> - This implementation does not support the {@code contains()</code> and <code>remove()} methods.
    041 * </p>
    042 */
    043
    044public class WrapDynaBean implements DynaBean {
    045
    046    private static final long serialVersionUID = 1L;
    047
    048    /**
    049     * The {@code DynaClass} "base class" that this DynaBean 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     * Constructs a new {@code DynaBean} associated with the specified JavaBean instance.
    060     *
    061     * @param instance JavaBean instance to be wrapped
    062     */
    063    public WrapDynaBean(final Object instance) {
    064        this(instance, null);
    065    }
    066
    067    /**
    068     * Creates a new instance of {@code WrapDynaBean}, associates it with the specified JavaBean instance, and initializes the bean's {@code DynaClass}. Using
    069     * this constructor this {@code WrapDynaBean} instance can be assigned a class which has been configured externally. If no {@code WrapDynaClass} is
    070     * provided, a new one is created using a standard mechanism.
    071     *
    072     * @param instance JavaBean instance to be wrapped
    073     * @param cls      the optional {@code WrapDynaClass} to be used for this bean
    074     * @since 1.9
    075     */
    076    public WrapDynaBean(final Object instance, final WrapDynaClass cls) {
    077        this.instance = instance;
    078        this.dynaClass = cls != null ? cls : (WrapDynaClass) getDynaClass();
    079    }
    080
    081    /**
    082     * Does the specified mapped property contain a value for the specified key value?
    083     *
    084     * @param name Name of the property to check
    085     * @param key  Name of the key to check
    086     * @return {@code true} if the mapped property contains a value for the specified key, otherwise {@code false}
    087     * @throws IllegalArgumentException if there is no property of the specified name
    088     */
    089    @Override
    090    public boolean contains(final String name, final String key) {
    091        throw new UnsupportedOperationException("WrapDynaBean does not support contains()");
    092    }
    093
    094    /**
    095     * Gets the value of a simple property with the specified name.
    096     *
    097     * @param name Name of the property whose value is to be retrieved
    098     * @return The property's value
    099     * @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}