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 */ 017package org.apache.commons.beanutils; 018 019/** 020 * <p>DynaClass which implements the <code>MutableDynaClass</code> interface.</p> 021 * 022 * <p>A <code>MutableDynaClass</code> is a specialized extension to <code>DynaClass</code> 023 * that allows properties to be added or removed dynamically.</p> 024 * 025 * <p>This implementation has one slightly unusual default behavior - calling 026 * the <code>getDynaProperty(name)</code> method for a property which doesn't 027 * exist returns a <code>DynaProperty</code> rather than <code>null</code>. The 028 * reason for this is that <code>BeanUtils</code> calls this method to check if 029 * a property exists before trying to set the value. This would defeat the object 030 * of the <code>LazyDynaBean</code> which automatically adds missing properties 031 * when any of its <code>set()</code> methods are called. For this reason the 032 * <code>isDynaProperty(name)</code> method has been added to this implementation 033 * in order to determine if a property actually exists. If the more <em>normal</em> 034 * behavior of returning <code>null</code> is required, then this can be achieved 035 * by calling the <code>setReturnNull(true)</code>.</p> 036 * 037 * <p>The <code>add(name, type, readable, writable)</code> method is not implemented 038 * and always throws an <code>UnsupportedOperationException</code>. I believe 039 * this attributes need to be added to the <code>DynaProperty</code> class 040 * in order to control read/write facilities.</p> 041 * 042 * @see LazyDynaBean 043 */ 044public class LazyDynaClass extends BasicDynaClass implements MutableDynaClass { 045 046 private static final long serialVersionUID = 1L; 047 048 /** 049 * Controls whether changes to this DynaClass's properties are allowed. 050 */ 051 protected boolean restricted; 052 053 /** 054 * <p>Controls whether the <code>getDynaProperty()</code> method returns 055 * null if a property doesn't exist - or creates a new one.</p> 056 * 057 * <p>Default is <code>false</code>. 058 */ 059 protected boolean returnNull; 060 061 /** 062 * Construct a new LazyDynaClass with default parameters. 063 */ 064 public LazyDynaClass() { 065 this(null, (DynaProperty[])null); 066 } 067 068 /** 069 * Construct a new LazyDynaClass with the specified name. 070 * 071 * @param name Name of this DynaBean class 072 */ 073 public LazyDynaClass(final String name) { 074 this(name, (DynaProperty[])null); 075 } 076 077 /** 078 * Construct a new LazyDynaClass with the specified name and DynaBean class. 079 * 080 * @param name Name of this DynaBean class 081 * @param dynaBeanClass The implementation class for new instances 082 */ 083 public LazyDynaClass(final String name, final Class<?> dynaBeanClass) { 084 this(name, dynaBeanClass, null); 085 } 086 087 /** 088 * Construct a new LazyDynaClass with the specified name, DynaBean class and properties. 089 * 090 * @param name Name of this DynaBean class 091 * @param dynaBeanClass The implementation class for new instances 092 * @param properties Property descriptors for the supported properties 093 */ 094 public LazyDynaClass(final String name, final Class<?> dynaBeanClass, final DynaProperty properties[]) { 095 super(name, dynaBeanClass, properties); 096 } 097 098 /** 099 * Construct a new LazyDynaClass with the specified name and properties. 100 * 101 * @param name Name of this DynaBean class 102 * @param properties Property descriptors for the supported properties 103 */ 104 public LazyDynaClass(final String name, final DynaProperty[] properties) { 105 this(name, LazyDynaBean.class, properties); 106 } 107 108 /** 109 * Add a new dynamic property. 110 * 111 * @param property Property the new dynamic property to add. 112 * @throws IllegalArgumentException if name is null 113 * @throws IllegalStateException if this DynaClass is currently 114 * restricted, so no new properties can be added 115 */ 116 protected void add(final DynaProperty property) { 117 118 if (property.getName() == null) { 119 throw new IllegalArgumentException("Property name is missing."); 120 } 121 122 if (isRestricted()) { 123 throw new IllegalStateException("DynaClass is currently restricted. No new properties can be added."); 124 } 125 126 // Check if property already exists 127 if (propertiesMap.get(property.getName()) != null) { 128 return; 129 } 130 131 // Create a new property array with the specified property 132 final DynaProperty[] oldProperties = getDynaProperties(); 133 final DynaProperty[] newProperties = new DynaProperty[oldProperties.length+1]; 134 System.arraycopy(oldProperties, 0, newProperties, 0, oldProperties.length); 135 newProperties[oldProperties.length] = property; 136 137 // Update the properties 138 setProperties(newProperties); 139 140 } 141 142 /** 143 * Add a new dynamic property with no restrictions on data type, 144 * readability, or writeability. 145 * 146 * @param name Name of the new dynamic property 147 * @throws IllegalArgumentException if name is null 148 * @throws IllegalStateException if this DynaClass is currently 149 * restricted, so no new properties can be added 150 */ 151 @Override 152 public void add(final String name) { 153 add(new DynaProperty(name)); 154 } 155 156 /** 157 * Add a new dynamic property with the specified data type, but with 158 * no restrictions on readability or writeability. 159 * 160 * @param name Name of the new dynamic property 161 * @param type Data type of the new dynamic property (null for no 162 * restrictions) 163 * 164 * @throws IllegalArgumentException if name is null 165 * @throws IllegalStateException if this DynaClass is currently 166 * restricted, so no new properties can be added 167 */ 168 @Override 169 public void add(final String name, final Class<?> type) { 170 if (type == null) { 171 add(name); 172 } else { 173 add(new DynaProperty(name, type)); 174 } 175 } 176 177 /** 178 * <p>Add a new dynamic property with the specified data type, readability, 179 * and writeability.</p> 180 * 181 * <p><strong>N.B.</strong>Support for readable/writeable properties has not been implemented 182 * and this method always throws a <code>UnsupportedOperationException</code>.</p> 183 * 184 * <p>I'm not sure the intention of the original authors for this method, but it seems to 185 * me that readable/writable should be attributes of the <code>DynaProperty</code> class 186 * (which they are not) and is the reason this method has not been implemented.</p> 187 * 188 * @param name Name of the new dynamic property 189 * @param type Data type of the new dynamic property (null for no 190 * restrictions) 191 * @param readable Set to <code>true</code> if this property value 192 * should be readable 193 * @param writeable Set to <code>true</code> if this property value 194 * should be writeable 195 * 196 * @throws UnsupportedOperationException anytime this method is called 197 */ 198 @Override 199 public void add(final String name, final Class<?> type, final boolean readable, final boolean writeable) { 200 throw new UnsupportedOperationException("readable/writable properties not supported"); 201 } 202 203 /** 204 * <p>Return a property descriptor for the specified property.</p> 205 * 206 * <p>If the property is not found and the <code>returnNull</code> indicator is 207 * <code>true</code>, this method always returns <code>null</code>.</p> 208 * 209 * <p>If the property is not found and the <code>returnNull</code> indicator is 210 * <code>false</code> a new property descriptor is created and returned (although 211 * its not actually added to the DynaClass's properties). This is the default 212 * beahviour.</p> 213 * 214 * <p>The reason for not returning a <code>null</code> property descriptor is that 215 * <code>BeanUtils</code> uses this method to check if a property exists 216 * before trying to set it - since these <em>Lazy</em> implementations automatically 217 * add any new properties when they are set, returning <code>null</code> from 218 * this method would defeat their purpose.</p> 219 * 220 * @param name Name of the dynamic property for which a descriptor 221 * is requested 222 * @return The dyna property for the specified name 223 * @throws IllegalArgumentException if no property name is specified 224 */ 225 @Override 226 public DynaProperty getDynaProperty(final String name) { 227 228 if (name == null) { 229 throw new IllegalArgumentException("Property name is missing."); 230 } 231 232 DynaProperty dynaProperty = propertiesMap.get(name); 233 234 // If it doesn't exist and returnNull is false 235 // create a new DynaProperty 236 if (dynaProperty == null && !isReturnNull() && !isRestricted()) { 237 dynaProperty = new DynaProperty(name); 238 } 239 240 return dynaProperty; 241 242 } 243 244 /** 245 * <p>Indicate whether a property actually exists.</p> 246 * 247 * <p><strong>N.B.</strong> Using <code>getDynaProperty(name) == null</code> 248 * doesn't work in this implementation because that method might 249 * return a DynaProperty if it doesn't exist (depending on the 250 * <code>returnNull</code> indicator).</p> 251 * 252 * @param name The name of the property to check 253 * @return <code>true</code> if there is a property of the 254 * specified name, otherwise <code>false</code> 255 * @throws IllegalArgumentException if no property name is specified 256 */ 257 public boolean isDynaProperty(final String name) { 258 259 if (name == null) { 260 throw new IllegalArgumentException("Property name is missing."); 261 } 262 263 return propertiesMap.get(name) == null ? false : true; 264 265 } 266 267 /** 268 * <p>Is this DynaClass currently restricted.</p> 269 * <p>If restricted, no changes to the existing registration of 270 * property names, data types, readability, or writeability are allowed.</p> 271 * @return <code>true</code> if this {@link MutableDynaClass} cannot be changed 272 * otherwise <code>false</code> 273 */ 274 @Override 275 public boolean isRestricted() { 276 return restricted; 277 } 278 279 /** 280 * Should this DynaClass return a <code>null</code> from 281 * the <code>getDynaProperty(name)</code> method if the property 282 * doesn't exist. 283 * 284 * @return <code>true</code> if a <code>null</code> {@link DynaProperty} 285 * should be returned if the property doesn't exist, otherwise 286 * <code>false</code> if a new {@link DynaProperty} should be created. 287 */ 288 public boolean isReturnNull() { 289 return returnNull; 290 } 291 292 /** 293 * Remove the specified dynamic property, and any associated data type, 294 * readability, and writeability, from this dynamic class. 295 * <strong>NOTE</strong> - This does <strong>NOT</strong> cause any 296 * corresponding property values to be removed from DynaBean instances 297 * associated with this DynaClass. 298 * 299 * @param name Name of the dynamic property to remove 300 * @throws IllegalArgumentException if name is null 301 * @throws IllegalStateException if this DynaClass is currently 302 * restricted, so no properties can be removed 303 */ 304 @Override 305 public void remove(final String name) { 306 307 if (name == null) { 308 throw new IllegalArgumentException("Property name is missing."); 309 } 310 311 if (isRestricted()) { 312 throw new IllegalStateException("DynaClass is currently restricted. No properties can be removed."); 313 } 314 315 // Ignore if property doesn't exist 316 if (propertiesMap.get(name) == null) { 317 return; 318 } 319 320 // Create a new property array of without the specified property 321 final DynaProperty[] oldProperties = getDynaProperties(); 322 final DynaProperty[] newProperties = new DynaProperty[oldProperties.length-1]; 323 int j = 0; 324 for (final DynaProperty oldProperty : oldProperties) { 325 if (!name.equals(oldProperty.getName())) { 326 newProperties[j] = oldProperty; 327 j++; 328 } 329 } 330 331 // Update the properties 332 setProperties(newProperties); 333 334 } 335 336 /** 337 * <p>Set whether this DynaClass is currently restricted.</p> 338 * <p>If restricted, no changes to the existing registration of 339 * property names, data types, readability, or writeability are allowed.</p> 340 * @param restricted <code>true</code> if this {@link MutableDynaClass} cannot 341 * be changed otherwise <code>false</code> 342 */ 343 @Override 344 public void setRestricted(final boolean restricted) { 345 this.restricted = restricted; 346 } 347 348 /** 349 * Set whether this DynaClass should return a <code>null</code> from 350 * the <code>getDynaProperty(name)</code> method if the property 351 * doesn't exist. 352 * @param returnNull <code>true</code> if a <code>null</code> {@link DynaProperty} 353 * should be returned if the property doesn't exist, otherwise 354 * <code>false</code> if a new {@link DynaProperty} should be created. 355 */ 356 public void setReturnNull(final boolean returnNull) { 357 this.returnNull = returnNull; 358 } 359 360}