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 * http://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 package org.apache.commons.beanutils; 18 19 import java.util.Map; 20 import java.util.List; 21 import java.util.ArrayList; 22 import java.util.Set; 23 import java.util.HashSet; 24 import java.util.Iterator; 25 import java.util.Collection; 26 import java.util.Collections; 27 28 /** 29 * <p>Decorates a {@link DynaBean} to provide <code>Map</code> behaviour.</p> 30 * 31 * <p>The motivation for this implementation is to provide access to {@link DynaBean} 32 * properties in technologies that are unaware of BeanUtils and {@link DynaBean}s - 33 * such as the expression languages of JSTL and JSF.</p> 34 * 35 * <p>This can be achieved either by wrapping the {@link DynaBean} prior to 36 * providing it to the technolody to process or by providing a <code>Map</code> 37 * accessor method on the DynaBean implementation: 38 * <pre><code> 39 * public Map getMap() { 40 * return new DynaBeanMapDecorator(this); 41 * }</code></pre> 42 * </ul> 43 * </p> 44 * 45 * <p>This, for example, could be used in JSTL in the following way to access 46 * a DynaBean's <code>fooProperty</code>: 47 * <ul><li><code>${myDynaBean.<b>map</b>.fooProperty}</code></li></ul> 48 * </p> 49 * 50 * <h3>Usage</h3> 51 * 52 * <p>To decorate a {@link DynaBean} simply instantiate this class with the 53 * target {@link DynaBean}:</p> 54 * 55 * <ul><li><code>Map fooMap = new DynaBeanMapDecorator(fooDynaBean);</code></li></ul> 56 * 57 * <p>The above example creates a <b><i>read only</i></b> <code>Map</code>. 58 * To create a <code>Map</code> which can be modified, construct a 59 * <code>DynaBeanMapDecorator</code> with the <b><i>read only</i></b> 60 * attribute set to <code>false</code>:</p> 61 * 62 * <ul><li><code>Map fooMap = new DynaBeanMapDecorator(fooDynaBean, false);</code></li></ul> 63 * 64 * <h3>Limitations</h3> 65 * <p>In this implementation the <code>entrySet()</code>, <code>keySet()</code> 66 * and <code>values()</code> methods create an <b><i>unmodifiable</i></b> 67 * <code>Set</code> and it does not support the Map's <code>clear()</code> 68 * and <code>remove()</code> operations.</p> 69 * 70 * @since BeanUtils 1.8.0 71 * @version $Revision: 546471 $ $Date: 2007-06-12 08:57:20 -0400 (Tue, 12 Jun 2007) $ 72 */ 73 public class DynaBeanMapDecorator implements Map { 74 75 private DynaBean dynaBean; 76 private boolean readOnly; 77 private transient Set keySet; 78 79 // ------------------- Constructors ---------------------------------- 80 81 /** 82 * Constructs a read only Map for the specified 83 * {@link DynaBean}. 84 * 85 * @param dynaBean The dyna bean being decorated 86 * @throws IllegalArgumentException if the {@link DynaBean} is null. 87 */ 88 public DynaBeanMapDecorator(DynaBean dynaBean) { 89 this(dynaBean, true); 90 } 91 92 /** 93 * Construct a Map for the specified {@link DynaBean}. 94 * 95 * @param dynaBean The dyna bean being decorated 96 * @param readOnly <code>true</code> if the Mpa is read only 97 * otherwise <code>false</code> 98 * @throws IllegalArgumentException if the {@link DynaBean} is null. 99 */ 100 public DynaBeanMapDecorator(DynaBean dynaBean, boolean readOnly) { 101 if (dynaBean == null) { 102 throw new IllegalArgumentException("DynaBean is null"); 103 } 104 this.dynaBean = dynaBean; 105 this.readOnly = readOnly; 106 } 107 108 109 // ------------------- public Methods -------------------------------- 110 111 112 /** 113 * Indicate whether the Map is read only. 114 * 115 * @return <code>true</code> if the Map is read only, 116 * otherwise <code>false</code>. 117 */ 118 public boolean isReadOnly() { 119 return readOnly; 120 } 121 122 // ------------------- java.util.Map Methods ------------------------- 123 124 /** 125 * clear() operation is not supported. 126 * 127 * @throws UnsupportedOperationException 128 */ 129 public void clear() { 130 throw new UnsupportedOperationException(); 131 } 132 133 /** 134 * Indicate whether the {@link DynaBean} contains a specified 135 * value for one (or more) of its properties. 136 * 137 * @param key The {@link DynaBean}'s property name 138 * @return <code>true</code> if one of the {@link DynaBean}'s 139 * properties contains a specified value. 140 */ 141 public boolean containsKey(Object key) { 142 DynaClass dynaClass = getDynaBean().getDynaClass(); 143 DynaProperty dynaProperty = dynaClass.getDynaProperty(toString(key)); 144 return (dynaProperty == null ? false : true); 145 } 146 147 /** 148 * Indicates whether the decorated {@link DynaBean} contains 149 * a specified value. 150 * 151 * @param value The value to check for. 152 * @return <code>true</code> if one of the the {@link DynaBean}'s 153 * properties contains the specified value, otherwise 154 * <code>false</code>. 155 */ 156 public boolean containsValue(Object value) { 157 DynaProperty[] properties = getDynaProperties(); 158 for (int i = 0; i < properties.length; i++) { 159 String key = properties[i].getName(); 160 Object prop = getDynaBean().get(key); 161 if (value == null) { 162 if (prop == null) { 163 return true; 164 } 165 } else { 166 if (value.equals(prop)) { 167 return true; 168 } 169 } 170 } 171 return false; 172 } 173 174 /** 175 * <p>Returns the Set of the property/value mappings 176 * in the decorated {@link DynaBean}.</p> 177 * 178 * <p>Each element in the Set is a <code>Map.Entry</code> 179 * type.</p> 180 * 181 * @return An unmodifiable set of the DynaBean 182 * property name/value pairs 183 */ 184 public Set entrySet() { 185 DynaProperty[] properties = getDynaProperties(); 186 Set set = new HashSet(properties.length); 187 for (int i = 0; i < properties.length; i++) { 188 String key = properties[i].getName(); 189 Object value = getDynaBean().get(key); 190 set.add(new MapEntry(key, value)); 191 } 192 return Collections.unmodifiableSet(set); 193 } 194 195 /** 196 * Return the value for the specified key from 197 * the decorated {@link DynaBean}. 198 * 199 * @param key The {@link DynaBean}'s property name 200 * @return The value for the specified property. 201 */ 202 public Object get(Object key) { 203 return getDynaBean().get(toString(key)); 204 } 205 206 /** 207 * Indicate whether the decorated {@link DynaBean} has 208 * any properties. 209 * 210 * @return <code>true</code> if the {@link DynaBean} has 211 * no properties, otherwise <code>false</code>. 212 */ 213 public boolean isEmpty() { 214 return (getDynaProperties().length == 0); 215 } 216 217 /** 218 * <p>Returns the Set of the property 219 * names in the decorated {@link DynaBean}.</p> 220 * 221 * <p><b>N.B.</b>For {@link DynaBean}s whose associated {@link DynaClass} 222 * is a {@link MutableDynaClass} a new Set is created every 223 * time, otherwise the Set is created only once and cached.</p> 224 * 225 * @return An unmodifiable set of the {@link DynaBean}s 226 * property names. 227 */ 228 public Set keySet() { 229 if (keySet != null) { 230 return keySet; 231 } 232 233 // Create a Set of the keys 234 DynaProperty[] properties = getDynaProperties(); 235 Set set = new HashSet(properties.length); 236 for (int i = 0; i < properties.length; i++) { 237 set.add(properties[i].getName()); 238 } 239 set = Collections.unmodifiableSet(set); 240 241 // Cache the keySet if Not a MutableDynaClass 242 DynaClass dynaClass = getDynaBean().getDynaClass(); 243 if (!(dynaClass instanceof MutableDynaClass)) { 244 keySet = set; 245 } 246 247 return set; 248 249 } 250 251 /** 252 * Set the value for the specified property in 253 * the decorated {@link DynaBean}. 254 * 255 * @param key The {@link DynaBean}'s property name 256 * @param value The value for the specified property. 257 * @return The previous property's value. 258 * @throws UnsupportedOperationException if 259 * <code>isReadOnly()</code> is true. 260 */ 261 public Object put(Object key, Object value) { 262 if (isReadOnly()) { 263 throw new UnsupportedOperationException("Map is read only"); 264 } 265 String property = toString(key); 266 Object previous = getDynaBean().get(property); 267 getDynaBean().set(property, value); 268 return previous; 269 } 270 271 /** 272 * Copy the contents of a Map to the decorated {@link DynaBean}. 273 * 274 * @param map The Map of values to copy. 275 * @throws UnsupportedOperationException if 276 * <code>isReadOnly()</code> is true. 277 */ 278 public void putAll(Map map) { 279 if (isReadOnly()) { 280 throw new UnsupportedOperationException("Map is read only"); 281 } 282 Iterator keys = map.keySet().iterator(); 283 while (keys.hasNext()) { 284 Object key = keys.next(); 285 put(key, map.get(key)); 286 } 287 } 288 289 /** 290 * remove() operation is not supported. 291 * 292 * @param key The {@link DynaBean}'s property name 293 * @return the value removed 294 * @throws UnsupportedOperationException 295 */ 296 public Object remove(Object key) { 297 throw new UnsupportedOperationException(); 298 } 299 300 /** 301 * Returns the number properties in the decorated 302 * {@link DynaBean}. 303 * @return The number of properties. 304 */ 305 public int size() { 306 return getDynaProperties().length; 307 } 308 309 /** 310 * Returns the set of property values in the 311 * decorated {@link DynaBean}. 312 * 313 * @return Unmodifiable collection of values. 314 */ 315 public Collection values() { 316 DynaProperty[] properties = getDynaProperties(); 317 List values = new ArrayList(properties.length); 318 for (int i = 0; i < properties.length; i++) { 319 String key = properties[i].getName(); 320 Object value = getDynaBean().get(key); 321 values.add(value); 322 } 323 return Collections.unmodifiableList(values); 324 } 325 326 // ------------------- protected Methods ----------------------------- 327 328 /** 329 * Provide access to the underlying {@link DynaBean} 330 * this Map decorates. 331 * 332 * @return the decorated {@link DynaBean}. 333 */ 334 public DynaBean getDynaBean() { 335 return dynaBean; 336 } 337 338 // ------------------- private Methods ------------------------------- 339 340 /** 341 * Convenience method to retrieve the {@link DynaProperty}s 342 * for this {@link DynaClass}. 343 * 344 * @return The an array of the {@link DynaProperty}s. 345 */ 346 private DynaProperty[] getDynaProperties() { 347 return getDynaBean().getDynaClass().getDynaProperties(); 348 } 349 350 /** 351 * Convenience method to convert an Object 352 * to a String. 353 * 354 * @param obj The Object to convert 355 * @return String representation of the object 356 */ 357 private String toString(Object obj) { 358 return (obj == null ? null : obj.toString()); 359 } 360 361 /** 362 * Map.Entry implementation. 363 */ 364 private static class MapEntry implements Map.Entry { 365 private Object key; 366 private Object value; 367 MapEntry(Object key, Object value) { 368 this.key = key; 369 this.value = value; 370 } 371 public boolean equals(Object o) { 372 if (!(o instanceof Map.Entry)) { 373 return false; 374 } 375 Map.Entry e = (Map.Entry)o; 376 return ((key.equals(e.getKey())) && 377 (value == null ? e.getValue() == null 378 : value.equals(e.getValue()))); 379 } 380 public int hashCode() { 381 return key.hashCode() + (value == null ? 0 : value.hashCode()); 382 } 383 public Object getKey() { 384 return key; 385 } 386 public Object getValue() { 387 return value; 388 } 389 public Object setValue(Object value) { 390 throw new UnsupportedOperationException(); 391 } 392 } 393 394 }