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.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}