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 package org.apache.commons.beanutils; 018 019 import java.util.Map; 020 import java.util.List; 021 import java.util.ArrayList; 022 import java.util.Set; 023 import java.util.HashSet; 024 import java.util.Iterator; 025 import java.util.Collection; 026 import java.util.Collections; 027 028 /** 029 * <p>Decorates a {@link DynaBean} to provide <code>Map</code> behaviour.</p> 030 * 031 * <p>The motivation for this implementation is to provide access to {@link DynaBean} 032 * properties in technologies that are unaware of BeanUtils and {@link DynaBean}s - 033 * such as the expression languages of JSTL and JSF.</p> 034 * 035 * <p>This can be achieved either by wrapping the {@link DynaBean} prior to 036 * providing it to the technolody to process or by providing a <code>Map</code> 037 * accessor method on the DynaBean implementation: 038 * <pre><code> 039 * public Map getMap() { 040 * return new DynaBeanMapDecorator(this); 041 * }</code></pre> 042 * </ul> 043 * </p> 044 * 045 * <p>This, for example, could be used in JSTL in the following way to access 046 * a DynaBean's <code>fooProperty</code>: 047 * <ul><li><code>${myDynaBean.<b>map</b>.fooProperty}</code></li></ul> 048 * </p> 049 * 050 * <h3>Usage</h3> 051 * 052 * <p>To decorate a {@link DynaBean} simply instantiate this class with the 053 * target {@link DynaBean}:</p> 054 * 055 * <ul><li><code>Map fooMap = new DynaBeanMapDecorator(fooDynaBean);</code></li></ul> 056 * 057 * <p>The above example creates a <b><i>read only</i></b> <code>Map</code>. 058 * To create a <code>Map</code> which can be modified, construct a 059 * <code>DynaBeanMapDecorator</code> with the <b><i>read only</i></b> 060 * attribute set to <code>false</code>:</p> 061 * 062 * <ul><li><code>Map fooMap = new DynaBeanMapDecorator(fooDynaBean, false);</code></li></ul> 063 * 064 * <h3>Limitations</h3> 065 * <p>In this implementation the <code>entrySet()</code>, <code>keySet()</code> 066 * and <code>values()</code> methods create an <b><i>unmodifiable</i></b> 067 * <code>Set</code> and it does not support the Map's <code>clear()</code> 068 * and <code>remove()</code> operations.</p> 069 * 070 * @since BeanUtils 1.8.0 071 * @version $Revision: 546471 $ $Date: 2007-06-12 13:57:20 +0100 (Tue, 12 Jun 2007) $ 072 */ 073 public class DynaBeanMapDecorator implements Map { 074 075 private DynaBean dynaBean; 076 private boolean readOnly; 077 private transient Set keySet; 078 079 // ------------------- Constructors ---------------------------------- 080 081 /** 082 * Constructs a read only Map for the specified 083 * {@link DynaBean}. 084 * 085 * @param dynaBean The dyna bean being decorated 086 * @throws IllegalArgumentException if the {@link DynaBean} is null. 087 */ 088 public DynaBeanMapDecorator(DynaBean dynaBean) { 089 this(dynaBean, true); 090 } 091 092 /** 093 * Construct a Map for the specified {@link DynaBean}. 094 * 095 * @param dynaBean The dyna bean being decorated 096 * @param readOnly <code>true</code> if the Mpa is read only 097 * otherwise <code>false</code> 098 * @throws IllegalArgumentException if the {@link DynaBean} is null. 099 */ 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 }