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 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.HashSet; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026 027/** 028 * <p>A base class for decorators providing <code>Map</code> behavior on 029 * {@link DynaBean}s.</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 rather technical base class implements the methods of the 036 * {@code Map} interface on top of a {@code DynaBean}. It was introduced 037 * to handle generic parameters in a meaningful way without breaking 038 * backwards compatibility of the {@link DynaBeanMapDecorator} class: A 039 * map wrapping a {@code DynaBean} should be of type {@code Map<String, Object>}. 040 * However, when using these generic parameters in {@code DynaBeanMapDecorator} 041 * this would be an incompatible change (as method signatures would have to 042 * be adapted). To solve this problem, this generic base class is added 043 * which allows specifying the key type as parameter. This makes it easy to 044 * have a new subclass using the correct generic parameters while 045 * {@code DynaBeanMapDecorator} could still remain with compatible 046 * parameters.</p> 047 * 048 * @param <K> the type of the keys in the decorated map 049 * @since BeanUtils 1.9.0 050 * @version $Id$ 051 */ 052public abstract class BaseDynaBeanMapDecorator<K> implements Map<K, Object> { 053 054 private final DynaBean dynaBean; 055 private final boolean readOnly; 056 private transient Set<K> keySet; 057 058 // ------------------- Constructors ---------------------------------- 059 060 /** 061 * Constructs a read only Map for the specified 062 * {@link DynaBean}. 063 * 064 * @param dynaBean The dyna bean being decorated 065 * @throws IllegalArgumentException if the {@link DynaBean} is null. 066 */ 067 public BaseDynaBeanMapDecorator(final DynaBean dynaBean) { 068 this(dynaBean, true); 069 } 070 071 /** 072 * Construct a Map for the specified {@link DynaBean}. 073 * 074 * @param dynaBean The dyna bean being decorated 075 * @param readOnly <code>true</code> if the Map is read only 076 * otherwise <code>false</code> 077 * @throws IllegalArgumentException if the {@link DynaBean} is null. 078 */ 079 public BaseDynaBeanMapDecorator(final DynaBean dynaBean, final boolean readOnly) { 080 if (dynaBean == null) { 081 throw new IllegalArgumentException("DynaBean is null"); 082 } 083 this.dynaBean = dynaBean; 084 this.readOnly = readOnly; 085 } 086 087 088 // ------------------- public Methods -------------------------------- 089 090 091 /** 092 * Indicate whether the Map is read only. 093 * 094 * @return <code>true</code> if the Map is read only, 095 * otherwise <code>false</code>. 096 */ 097 public boolean isReadOnly() { 098 return readOnly; 099 } 100 101 // ------------------- java.util.Map Methods ------------------------- 102 103 /** 104 * clear() operation is not supported. 105 * 106 * @throws UnsupportedOperationException 107 */ 108 public void clear() { 109 throw new UnsupportedOperationException(); 110 } 111 112 /** 113 * Indicate whether the {@link DynaBean} contains a specified 114 * value for one (or more) of its properties. 115 * 116 * @param key The {@link DynaBean}'s property name 117 * @return <code>true</code> if one of the {@link DynaBean}'s 118 * properties contains a specified value. 119 */ 120 public boolean containsKey(final Object key) { 121 final DynaClass dynaClass = getDynaBean().getDynaClass(); 122 final DynaProperty dynaProperty = dynaClass.getDynaProperty(toString(key)); 123 return (dynaProperty == null ? false : true); 124 } 125 126 /** 127 * Indicates whether the decorated {@link DynaBean} contains 128 * a specified value. 129 * 130 * @param value The value to check for. 131 * @return <code>true</code> if one of the the {@link DynaBean}'s 132 * properties contains the specified value, otherwise 133 * <code>false</code>. 134 */ 135 public boolean containsValue(final Object value) { 136 final DynaProperty[] properties = getDynaProperties(); 137 for (DynaProperty propertie : properties) { 138 final String key = propertie.getName(); 139 final Object prop = getDynaBean().get(key); 140 if (value == null) { 141 if (prop == null) { 142 return true; 143 } 144 } else { 145 if (value.equals(prop)) { 146 return true; 147 } 148 } 149 } 150 return false; 151 } 152 153 /** 154 * <p>Returns the Set of the property/value mappings 155 * in the decorated {@link DynaBean}.</p> 156 * 157 * <p>Each element in the Set is a <code>Map.Entry</code> 158 * type.</p> 159 * 160 * @return An unmodifiable set of the DynaBean 161 * property name/value pairs 162 */ 163 public Set<Map.Entry<K, Object>> entrySet() { 164 final DynaProperty[] properties = getDynaProperties(); 165 final Set<Map.Entry<K, Object>> set = new HashSet<Map.Entry<K, Object>>(properties.length); 166 for (DynaProperty propertie : properties) { 167 final K key = convertKey(propertie.getName()); 168 final Object value = getDynaBean().get(propertie.getName()); 169 set.add(new MapEntry<K>(key, value)); 170 } 171 return Collections.unmodifiableSet(set); 172 } 173 174 /** 175 * Return the value for the specified key from 176 * the decorated {@link DynaBean}. 177 * 178 * @param key The {@link DynaBean}'s property name 179 * @return The value for the specified property. 180 */ 181 public Object get(final Object key) { 182 return getDynaBean().get(toString(key)); 183 } 184 185 /** 186 * Indicate whether the decorated {@link DynaBean} has 187 * any properties. 188 * 189 * @return <code>true</code> if the {@link DynaBean} has 190 * no properties, otherwise <code>false</code>. 191 */ 192 public boolean isEmpty() { 193 return (getDynaProperties().length == 0); 194 } 195 196 /** 197 * <p>Returns the Set of the property 198 * names in the decorated {@link DynaBean}.</p> 199 * 200 * <p><b>N.B.</b>For {@link DynaBean}s whose associated {@link DynaClass} 201 * is a {@link MutableDynaClass} a new Set is created every 202 * time, otherwise the Set is created only once and cached.</p> 203 * 204 * @return An unmodifiable set of the {@link DynaBean}s 205 * property names. 206 */ 207 public Set<K> keySet() { 208 if (keySet != null) { 209 return keySet; 210 } 211 212 // Create a Set of the keys 213 final DynaProperty[] properties = getDynaProperties(); 214 Set<K> set = new HashSet<K>(properties.length); 215 for (DynaProperty propertie : properties) { 216 set.add(convertKey(propertie.getName())); 217 } 218 set = Collections.unmodifiableSet(set); 219 220 // Cache the keySet if Not a MutableDynaClass 221 final DynaClass dynaClass = getDynaBean().getDynaClass(); 222 if (!(dynaClass instanceof MutableDynaClass)) { 223 keySet = set; 224 } 225 226 return set; 227 228 } 229 230 /** 231 * Set the value for the specified property in 232 * the decorated {@link DynaBean}. 233 * 234 * @param key The {@link DynaBean}'s property name 235 * @param value The value for the specified property. 236 * @return The previous property's value. 237 * @throws UnsupportedOperationException if 238 * <code>isReadOnly()</code> is true. 239 */ 240 public Object put(final K key, final Object value) { 241 if (isReadOnly()) { 242 throw new UnsupportedOperationException("Map is read only"); 243 } 244 final String property = toString(key); 245 final Object previous = getDynaBean().get(property); 246 getDynaBean().set(property, value); 247 return previous; 248 } 249 250 /** 251 * Copy the contents of a Map to the decorated {@link DynaBean}. 252 * 253 * @param map The Map of values to copy. 254 * @throws UnsupportedOperationException if 255 * <code>isReadOnly()</code> is true. 256 */ 257 public void putAll(final Map<? extends K, ? extends Object> map) { 258 if (isReadOnly()) { 259 throw new UnsupportedOperationException("Map is read only"); 260 } 261 for (final Map.Entry<? extends K, ?> e : map.entrySet()) { 262 put(e.getKey(), e.getValue()); 263 } 264 } 265 266 /** 267 * remove() operation is not supported. 268 * 269 * @param key The {@link DynaBean}'s property name 270 * @return the value removed 271 * @throws UnsupportedOperationException 272 */ 273 public Object remove(final Object key) { 274 throw new UnsupportedOperationException(); 275 } 276 277 /** 278 * Returns the number properties in the decorated 279 * {@link DynaBean}. 280 * @return The number of properties. 281 */ 282 public int size() { 283 return getDynaProperties().length; 284 } 285 286 /** 287 * Returns the set of property values in the 288 * decorated {@link DynaBean}. 289 * 290 * @return Unmodifiable collection of values. 291 */ 292 public Collection<Object> values() { 293 final DynaProperty[] properties = getDynaProperties(); 294 final List<Object> values = new ArrayList<Object>(properties.length); 295 for (DynaProperty propertie : properties) { 296 final String key = propertie.getName(); 297 final Object value = getDynaBean().get(key); 298 values.add(value); 299 } 300 return Collections.unmodifiableList(values); 301 } 302 303 // ------------------- protected Methods ----------------------------- 304 305 /** 306 * Provide access to the underlying {@link DynaBean} 307 * this Map decorates. 308 * 309 * @return the decorated {@link DynaBean}. 310 */ 311 public DynaBean getDynaBean() { 312 return dynaBean; 313 } 314 315 /** 316 * Converts the name of a property to the key type of this decorator. 317 * 318 * @param propertyName the name of a property 319 * @return the converted key to be used in the decorated map 320 */ 321 protected abstract K convertKey(String propertyName); 322 323 // ------------------- private Methods ------------------------------- 324 325 /** 326 * Convenience method to retrieve the {@link DynaProperty}s 327 * for this {@link DynaClass}. 328 * 329 * @return The an array of the {@link DynaProperty}s. 330 */ 331 private DynaProperty[] getDynaProperties() { 332 return getDynaBean().getDynaClass().getDynaProperties(); 333 } 334 335 /** 336 * Convenience method to convert an Object 337 * to a String. 338 * 339 * @param obj The Object to convert 340 * @return String representation of the object 341 */ 342 private String toString(final Object obj) { 343 return (obj == null ? null : obj.toString()); 344 } 345 346 /** 347 * Map.Entry implementation. 348 */ 349 private static class MapEntry<K> implements Map.Entry<K, Object> { 350 private final K key; 351 private final Object value; 352 MapEntry(final K key, final Object value) { 353 this.key = key; 354 this.value = value; 355 } 356 @Override 357 public boolean equals(final Object o) { 358 if (!(o instanceof Map.Entry)) { 359 return false; 360 } 361 final Map.Entry<?, ?> e = (Map.Entry<?, ?>)o; 362 return ((key.equals(e.getKey())) && 363 (value == null ? e.getValue() == null 364 : value.equals(e.getValue()))); 365 } 366 @Override 367 public int hashCode() { 368 return key.hashCode() + (value == null ? 0 : value.hashCode()); 369 } 370 public K getKey() { 371 return key; 372 } 373 public Object getValue() { 374 return value; 375 } 376 public Object setValue(final Object value) { 377 throw new UnsupportedOperationException(); 378 } 379 } 380 381}