001 /* 002 * Copyright 2002-2004 The Apache Software Foundation 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.apache.commons.clazz.reflect.extended; 017 018 import java.lang.reflect.Array; 019 import java.lang.reflect.InvocationTargetException; 020 import java.lang.reflect.Method; 021 import java.util.AbstractMap; 022 import java.util.AbstractSet; 023 import java.util.Arrays; 024 import java.util.Collection; 025 import java.util.Collections; 026 import java.util.HashMap; 027 import java.util.HashSet; 028 import java.util.Iterator; 029 import java.util.Map; 030 import java.util.Set; 031 032 import org.apache.commons.clazz.ClazzAccessException; 033 034 035 /** 036 * This is an implementation of the <code>Map</code> interface 037 * that is based on a Mapped property. Whenever possible, it 038 * uses concrete methods on the owner of the property to manipulate the map. 039 * <p> 040 * Consider the following example: 041 * <pre> 042 * Map map = (Map)clazz.getProperty("fooMap").get(instance); 043 * Object value = map.get("bar"); 044 * </pre> 045 * 046 * If <code>instance</code> has a <code>getFoo(String key)</code> method, 047 * this code will implicitly invoke it like this: <code>getFoo("bar")</code>. 048 * otherwise it will obtain the whole map and extract the 049 * requested value. 050 * 051 * @author <a href="mailto:dmitri@apache.org">Dmitri Plotnikov</a> 052 * @version $Id: ReflectedMap.java 155436 2005-02-26 13:17:48Z dirkv $ 053 */ 054 public class ReflectedMap extends AbstractMap { 055 private Object instance; 056 private ReflectedMappedProperty property; 057 private int modCount = 0; 058 059 /** 060 * Constructor for ReflectedMap. 061 */ 062 public ReflectedMap( 063 Object instance, 064 ReflectedMappedProperty property) 065 { 066 this.instance = instance; 067 this.property = property; 068 } 069 070 public Map getPropertyValue() { 071 Method readMethod = property.getReadMethod(); 072 if (readMethod == null) { 073 throw new ClazzAccessException( 074 "Cannot read property " 075 + property.getName() 076 + ": no read method"); 077 } 078 try { 079 return (Map) readMethod.invoke(instance, null); 080 } 081 catch (Exception ex) { 082 throw accessException("Cannot read property", readMethod, ex); 083 } 084 } 085 086 public void setPropertyValue(Map value) { 087 Method writeMethod = property.getWriteMethod(); 088 if (writeMethod == null) { 089 throw new ClazzAccessException( 090 "Cannot set property: " 091 + property.getName() 092 + ": no set(array) method"); 093 } 094 095 try { 096 writeMethod.invoke(instance, new Object[] { value }); 097 } 098 catch (Exception ex) { 099 throw accessException("Cannot set property", writeMethod, ex); 100 } 101 } 102 103 public Set getPropertyKeySet() { 104 Method keySetMethod = property.getKeySetMethod(); 105 if (keySetMethod != null) { 106 Set set; 107 try { 108 Object value = keySetMethod.invoke(instance, null); 109 if (value == null) { 110 set = Collections.EMPTY_SET; 111 } 112 else if (value instanceof Set) { 113 set = (Set) value; 114 } 115 else if (value instanceof Collection) { 116 set = new ConcurrentChangeSafeSet((Collection) value); 117 } 118 else { 119 set = 120 new ConcurrentChangeSafeSet( 121 Arrays.asList((Object[]) value)); 122 } 123 } 124 catch (Exception ex) { 125 throw new ClazzAccessException( 126 "Cannot get key set: " 127 + property.getName() 128 + ": cannot invoke method: " 129 + keySetMethod.getName(), 130 ex); 131 } 132 return set; 133 } 134 else { 135 Map map = getPropertyValue(); 136 if (map == null) { 137 return Collections.EMPTY_SET; 138 } 139 return map.keySet(); 140 } 141 } 142 143 /** 144 * If there is a getFoo(key) method, calls that for every key. 145 * Otherwise, calls getFooMap().get(key) 146 * 147 * @see java.util.Map#get(java.lang.Object) 148 */ 149 public Object get(Object key) { 150 Method getMethod = property.getGetMethod(); 151 if (getMethod != null) { 152 Object value; 153 try { 154 value = getMethod.invoke(instance, new Object[]{key}); 155 } 156 catch (Exception ex) { 157 throw new ClazzAccessException( 158 "Cannot get property : " 159 + property.getName() 160 + ": cannot invoke method: " 161 + getMethod.getName(), 162 ex); 163 } 164 return value; 165 } 166 else { 167 Map map = getPropertyValue(); 168 if (map == null) { 169 return null; 170 } 171 return map.get(key); 172 } 173 } 174 175 /** 176 * @see java.util.Map#size() 177 */ 178 public int size() { 179 Method keySetMethod = property.getKeySetMethod(); 180 if (keySetMethod != null) { 181 try { 182 Object value = keySetMethod.invoke(instance, null); 183 if (value == null) { 184 return 0; 185 } 186 else if (value instanceof Collection) { 187 return ((Collection) value).size(); 188 } 189 else { 190 return Array.getLength(value); 191 } 192 } 193 catch (Exception ex) { 194 throw accessException("Cannot get key set", keySetMethod, ex); 195 } 196 } 197 else { 198 Map map = getPropertyValue(); 199 if (map == null) { 200 return 0; 201 } 202 return map.size(); 203 } 204 } 205 206 /** 207 * @see java.util.Map#isEmpty() 208 */ 209 public boolean isEmpty() { 210 return size() == 0; 211 } 212 213 /** 214 * @see java.util.Map#keySet() 215 */ 216 public Set keySet() { 217 return new EntrySet(KEYS); 218 } 219 220 /** 221 * @see java.util.Map#entrySet() 222 */ 223 public Set entrySet() { 224 return new EntrySet(ENTRIES); 225 } 226 227 /** 228 * @see java.util.Map#put(java.lang.Object, java.lang.Object) 229 */ 230 public Object put(Object key, Object value) { 231 Method putMethod = property.getPutMethod(); 232 if (putMethod != null) { 233 Object oldValue = null; 234 try { 235 oldValue = get(key); 236 } 237 catch (Throwable t) { 238 // Ignore 239 } 240 241 try { 242 putMethod.invoke(instance, new Object[]{key, value}); 243 } 244 catch (Exception ex) { 245 throw new ClazzAccessException( 246 "Cannot set property : " 247 + property.getName() 248 + ": cannot invoke method: " 249 + putMethod.getName(), 250 ex); 251 } 252 return oldValue; 253 } 254 else { 255 Map map = getPropertyValue(); 256 if (map == null) { 257 map = new HashMap(); 258 setPropertyValue(map); 259 } 260 return map.put(key, value); 261 } 262 } 263 264 /** 265 * @see java.util.Map#remove(java.lang.Object) 266 */ 267 public Object remove(Object key) { 268 Method removeMethod = property.getRemoveMethod(); 269 if (removeMethod != null) { 270 Object oldValue = null; 271 try { 272 oldValue = get(key); 273 } 274 catch (Throwable t) { 275 // Ignore 276 } 277 278 try { 279 removeMethod.invoke(instance, new Object[]{key}); 280 } 281 catch (Exception ex) { 282 throw new ClazzAccessException( 283 "Cannot set property : " 284 + property.getName() 285 + ": cannot invoke method: " 286 + removeMethod.getName(), 287 ex); 288 } 289 return oldValue; 290 } 291 else { 292 Map map = getPropertyValue(); 293 if (map != null) { 294 return map.remove(key); 295 } 296 return null; 297 } 298 } 299 300 private RuntimeException accessException( 301 String message, 302 Method method, 303 Throwable ex) 304 { 305 if (ex instanceof InvocationTargetException) { 306 ex = ((InvocationTargetException) ex).getTargetException(); 307 } 308 309 // Just re-throw all runtime exceptions - there is really no 310 // point in wrapping them 311 if (ex instanceof RuntimeException) { 312 throw (RuntimeException) ex; 313 } 314 if (ex instanceof Error) { 315 throw (Error) ex; 316 } 317 318 throw new ClazzAccessException( 319 message 320 + ": " 321 + property.getName() 322 + ": cannot invoke method: " 323 + method.getName(), 324 ex); 325 } 326 327 private static final int ENTRIES = 0; 328 private static final int KEYS = 1; 329 330 /** 331 * An implementation of Set that delegates object deletion to the 332 * encompassing ReflectedMap. 333 */ 334 private class EntrySet extends AbstractSet { 335 private int type; 336 private int modCount = -1; 337 private Set keySet; 338 private int size; 339 340 public EntrySet(int type) { 341 this.type = type; 342 update(); 343 } 344 345 public void refresh() { 346 // If EntrySet is out of sync with the 347 // parent ReflectedMap, update the cached key set and size 348 if (modCount != ReflectedMap.this.modCount) { 349 update(); 350 } 351 } 352 353 public void update() { 354 // Make sure modCount of EntrySet is maintained in sync with 355 // that of the embracing List 356 modCount = ReflectedMap.this.modCount; 357 358 keySet = ReflectedMap.this.getPropertyKeySet(); 359 size = keySet.size(); 360 } 361 362 public int size() { 363 refresh(); 364 return size; 365 } 366 367 public Iterator iterator() { 368 refresh(); 369 return new EntryIterator(keySet, type); 370 } 371 372 public boolean remove(Object object) { 373 refresh(); 374 Object key = 375 (type == KEYS ? object : ((Map.Entry) object).getKey()); 376 377 boolean exists = true; 378 try { 379 exists = keySet.contains(key); 380 } 381 catch (Throwable t) { 382 // Ignore 383 } 384 if (exists) { 385 ReflectedMap.this.remove(key); 386 } 387 return exists; 388 } 389 } 390 391 /** 392 * An implementation of Iterator that delegates object deletion to the 393 * encompassing ReflectedMap. 394 */ 395 private class EntryIterator implements Iterator { 396 private int type; 397 private Set keySet; 398 private Iterator keyIterator; 399 private Object lastReturned = UNINITIALIZED; 400 401 public EntryIterator(Set keySet, int type) { 402 this.type = type; 403 this.keySet = keySet; 404 this.keyIterator = keySet.iterator(); 405 } 406 407 /** 408 * @see java.util.Iterator#hasNext() 409 */ 410 public boolean hasNext() { 411 return keyIterator.hasNext(); 412 } 413 414 /** 415 * @see java.util.Iterator#next() 416 */ 417 public Object next() { 418 lastReturned = keyIterator.next(); 419 if (type == KEYS) { 420 return lastReturned; 421 } 422 else { 423 return new Entry(lastReturned); 424 } 425 } 426 427 /** 428 * @see java.util.Iterator#remove() 429 */ 430 public void remove() { 431 if (lastReturned == UNINITIALIZED) { 432 throw new IllegalStateException(); 433 } 434 ensureConcurrentChangeSafety(); 435 ReflectedMap.this.remove(lastReturned); 436 } 437 438 /** 439 * This is called when we are about to delete a key from the map. 440 * The method checks if the set of keys we are iterating over is in fact 441 * a copy of the set of keys of the original collection. If it is not, 442 * the method creates such copy and re-executes the iteration steps that 443 * have already been made. The assumption made here is that as long as 444 * set remains unchanged, an iteration will always present elements in 445 * the same order (which is not to say that the order is predicatble). 446 */ 447 private void ensureConcurrentChangeSafety() { 448 if (!(keySet instanceof ConcurrentChangeSafeSet)) { 449 keySet = new ConcurrentChangeSafeSet(keySet); 450 keyIterator = keySet.iterator(); 451 while (keyIterator.hasNext()) { 452 Object key = keyIterator.next(); 453 if ((key == null && lastReturned == null) 454 || (key != null && key.equals(lastReturned))) { 455 return; 456 } 457 } 458 throw new IllegalStateException( 459 "The second iteration over the key set" 460 + " did not produce the same elements"); 461 } 462 } 463 } 464 465 private static final Object UNINITIALIZED = new Object(); 466 467 /** 468 * An implementation of Map.Entry that maintains a key and gets the value 469 * from the property, if needed. 470 */ 471 private class Entry implements Map.Entry { 472 private Object key; 473 474 public Entry(Object key) { 475 this.key = key; 476 } 477 478 /** 479 * @see java.util.Map.Entry#getKey() 480 */ 481 public Object getKey() { 482 return key; 483 } 484 /** 485 * @see java.util.Map.Entry#getValue() 486 */ 487 public Object getValue() { 488 return ReflectedMap.this.get(key); 489 } 490 491 /** 492 * @see java.util.Map.Entry#setValue(java.lang.Object) 493 */ 494 public Object setValue(Object value) { 495 return ReflectedMap.this.put(key, value); 496 } 497 498 public boolean equals(Object o) { 499 if (!(o instanceof Map.Entry)) { 500 return false; 501 } 502 503 Map.Entry e = (Map.Entry) o; 504 return (key == null ? e.getKey() == null : key.equals(e.getKey())); 505 } 506 507 public int hashCode() { 508 return key == null ? 0 : key.hashCode(); 509 } 510 511 public String toString() { 512 return key + "=" + getValue(); 513 } 514 } 515 516 /** 517 * This is a simple HashSet. We are only introducing the subclass as a 518 * marker of the fact that we created the set rather than getting it 519 * directly from the map value of the property. 520 */ 521 private static class ConcurrentChangeSafeSet extends HashSet { 522 public ConcurrentChangeSafeSet(Collection collection) { 523 super(collection); 524 } 525 } 526 }