Coverage Report - org.apache.commons.clazz.reflect.extended.ReflectedMap
 
Classes in this File Line Coverage Branch Coverage Complexity
ReflectedMap
0%
0/105
0%
0/42
3.161
ReflectedMap$ConcurrentChangeSafeSet
0%
0/2
N/A
3.161
ReflectedMap$Entry
0%
0/12
0%
0/8
3.161
ReflectedMap$EntryIterator
0%
0/26
0%
0/16
3.161
ReflectedMap$EntrySet
0%
0/25
0%
0/6
3.161
 
 1  
 /*
 2  
  * Copyright 2002-2004 The Apache Software Foundation
 3  
  *
 4  
  * Licensed under the Apache License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  *     http://www.apache.org/licenses/LICENSE-2.0
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 package org.apache.commons.clazz.reflect.extended;
 17  
 
 18  
 import java.lang.reflect.Array;
 19  
 import java.lang.reflect.InvocationTargetException;
 20  
 import java.lang.reflect.Method;
 21  
 import java.util.AbstractMap;
 22  
 import java.util.AbstractSet;
 23  
 import java.util.Arrays;
 24  
 import java.util.Collection;
 25  
 import java.util.Collections;
 26  
 import java.util.HashMap;
 27  
 import java.util.HashSet;
 28  
 import java.util.Iterator;
 29  
 import java.util.Map;
 30  
 import java.util.Set;
 31  
 
 32  
 import org.apache.commons.clazz.ClazzAccessException;
 33  
 
 34  
 
 35  
 /**
 36  
  * This is an implementation of the <code>Map</code> interface
 37  
  * that is based on a Mapped property.  Whenever possible, it
 38  
  * uses concrete methods on the owner of the property to manipulate the map.
 39  
  * <p>
 40  
  * Consider the following example:
 41  
  * <pre>
 42  
  *      Map map = (Map)clazz.getProperty("fooMap").get(instance);
 43  
  *      Object value = map.get("bar");
 44  
  * </pre>
 45  
  * 
 46  
  * If <code>instance</code> has a <code>getFoo(String key)</code> method,
 47  
  * this code will implicitly invoke it like this: <code>getFoo("bar")</code>.
 48  
  * otherwise it will obtain the whole map and extract the 
 49  
  * requested value.
 50  
  * 
 51  
  * @author <a href="mailto:dmitri@apache.org">Dmitri Plotnikov</a>
 52  
  * @version $Id: ReflectedMap.java 155436 2005-02-26 13:17:48Z dirkv $
 53  
  */
 54  0
 public class ReflectedMap extends AbstractMap {
 55  
     private Object instance;
 56  
     private ReflectedMappedProperty property;
 57  0
     private int modCount = 0;
 58  
     
 59  
     /**
 60  
      * Constructor for ReflectedMap.
 61  
      */
 62  
     public ReflectedMap(
 63  
             Object instance,
 64  
             ReflectedMappedProperty property) 
 65  0
     {
 66  0
         this.instance = instance;
 67  0
         this.property = property;
 68  0
     }
 69  
     
 70  
     public Map getPropertyValue() {
 71  0
         Method readMethod = property.getReadMethod();
 72  0
         if (readMethod == null) {
 73  0
             throw new ClazzAccessException(
 74  
                 "Cannot read property "
 75  
                     + property.getName()
 76  
                     + ": no read method");
 77  
         }
 78  
         try {
 79  0
             return (Map) readMethod.invoke(instance, null);
 80  
         }
 81  0
         catch (Exception ex) {
 82  0
             throw accessException("Cannot read property", readMethod, ex);
 83  
         }
 84  
     }
 85  
 
 86  
     public void setPropertyValue(Map value) {
 87  0
         Method writeMethod = property.getWriteMethod();
 88  0
         if (writeMethod == null) {
 89  0
             throw new ClazzAccessException(
 90  
                 "Cannot set property: "
 91  
                     + property.getName()
 92  
                     + ": no set(array) method");
 93  
         }
 94  
 
 95  
         try {
 96  0
             writeMethod.invoke(instance, new Object[] { value });
 97  
         }
 98  0
         catch (Exception ex) {
 99  0
             throw accessException("Cannot set property", writeMethod, ex);
 100  0
         }
 101  0
     }
 102  
 
 103  
     public Set getPropertyKeySet() {
 104  0
         Method keySetMethod = property.getKeySetMethod();
 105  0
         if (keySetMethod != null) {
 106  
             Set set;
 107  
             try {
 108  0
                 Object value = keySetMethod.invoke(instance, null);
 109  0
                 if (value == null) {
 110  0
                     set = Collections.EMPTY_SET;
 111  
                 }
 112  0
                 else if (value instanceof Set) {
 113  0
                     set = (Set) value;
 114  
                 }
 115  0
                 else if (value instanceof Collection) {
 116  0
                     set = new ConcurrentChangeSafeSet((Collection) value);
 117  
                 }
 118  
                 else {
 119  0
                     set =
 120  
                         new ConcurrentChangeSafeSet(
 121  
                             Arrays.asList((Object[]) value));
 122  
                 }
 123  
             }
 124  0
             catch (Exception ex) {
 125  0
                 throw new ClazzAccessException(
 126  
                     "Cannot get key set: "
 127  
                         + property.getName()
 128  
                         + ": cannot invoke method: "
 129  
                         + keySetMethod.getName(),
 130  
                     ex);
 131  0
             }
 132  0
             return set;
 133  
         }
 134  
         else {
 135  0
             Map map = getPropertyValue();
 136  0
             if (map == null) {
 137  0
                 return Collections.EMPTY_SET;
 138  
             }
 139  0
             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  0
         Method getMethod = property.getGetMethod();
 151  0
         if (getMethod != null) {
 152  
             Object value;
 153  
             try {
 154  0
                 value = getMethod.invoke(instance, new Object[]{key});
 155  
             }
 156  0
             catch (Exception ex) {
 157  0
                 throw new ClazzAccessException(
 158  
                     "Cannot get property : "
 159  
                         + property.getName()
 160  
                         + ": cannot invoke method: "
 161  
                         + getMethod.getName(),
 162  
                     ex);
 163  0
             }
 164  0
             return value;
 165  
         }
 166  
         else {
 167  0
             Map map = getPropertyValue();
 168  0
             if (map == null) {
 169  0
                 return null;
 170  
             }
 171  0
             return map.get(key);
 172  
         }
 173  
     }
 174  
 
 175  
     /**
 176  
      * @see java.util.Map#size()
 177  
      */
 178  
     public int size() {
 179  0
         Method keySetMethod = property.getKeySetMethod();
 180  0
         if (keySetMethod != null) {
 181  
             try {
 182  0
                 Object value = keySetMethod.invoke(instance, null);
 183  0
                 if (value == null) {
 184  0
                     return 0;
 185  
                 }
 186  0
                 else if (value instanceof Collection) {
 187  0
                     return ((Collection) value).size();
 188  
                 }
 189  
                 else {
 190  0
                     return Array.getLength(value);
 191  
                 }
 192  
             }
 193  0
             catch (Exception ex) {
 194  0
                 throw accessException("Cannot get key set", keySetMethod, ex);
 195  
             }
 196  
         }
 197  
         else {
 198  0
             Map map = getPropertyValue();
 199  0
             if (map == null) {
 200  0
                 return 0;
 201  
             }
 202  0
             return map.size();
 203  
         }
 204  
     }
 205  
 
 206  
     /**
 207  
      * @see java.util.Map#isEmpty()
 208  
      */
 209  
     public boolean isEmpty() {
 210  0
         return size() == 0;
 211  
     }
 212  
 
 213  
     /**
 214  
      * @see java.util.Map#keySet()
 215  
      */
 216  
     public Set keySet() {
 217  0
         return new EntrySet(KEYS);
 218  
     }
 219  
     
 220  
     /**
 221  
      * @see java.util.Map#entrySet()
 222  
      */
 223  
     public Set entrySet() {
 224  0
         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  0
         Method putMethod = property.getPutMethod();
 232  0
         if (putMethod != null) {
 233  0
             Object oldValue = null;
 234  
             try {
 235  0
                 oldValue = get(key);
 236  
             }
 237  0
             catch (Throwable t) {
 238  
                 // Ignore
 239  0
             }
 240  
             
 241  
             try {
 242  0
                 putMethod.invoke(instance, new Object[]{key, value});
 243  
             }
 244  0
             catch (Exception ex) {
 245  0
                 throw new ClazzAccessException(
 246  
                     "Cannot set property : "
 247  
                         + property.getName()
 248  
                         + ": cannot invoke method: "
 249  
                         + putMethod.getName(),
 250  
                     ex);
 251  0
             }
 252  0
             return oldValue;
 253  
         }
 254  
         else {
 255  0
             Map map = getPropertyValue();
 256  0
             if (map == null) {
 257  0
                 map = new HashMap();
 258  0
                 setPropertyValue(map);
 259  
             }
 260  0
             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  0
         Method removeMethod = property.getRemoveMethod();
 269  0
         if (removeMethod != null) {
 270  0
             Object oldValue = null;
 271  
             try {
 272  0
                 oldValue = get(key);
 273  
             }
 274  0
             catch (Throwable t) {
 275  
                 // Ignore
 276  0
             }
 277  
 
 278  
             try {
 279  0
                 removeMethod.invoke(instance, new Object[]{key});
 280  
             }
 281  0
             catch (Exception ex) {
 282  0
                 throw new ClazzAccessException(
 283  
                     "Cannot set property : "
 284  
                         + property.getName()
 285  
                         + ": cannot invoke method: "
 286  
                         + removeMethod.getName(),
 287  
                     ex);
 288  0
             }
 289  0
             return oldValue;
 290  
         }
 291  
         else {
 292  0
             Map map = getPropertyValue();
 293  0
             if (map != null) {
 294  0
                 return map.remove(key);
 295  
             }
 296  0
             return null;
 297  
         }
 298  
     }
 299  
     
 300  
     private RuntimeException accessException(
 301  
         String message,
 302  
         Method method,
 303  
         Throwable ex)
 304  
     {
 305  0
         if (ex instanceof InvocationTargetException) {
 306  0
             ex = ((InvocationTargetException) ex).getTargetException();
 307  
         }
 308  
 
 309  
         // Just re-throw all runtime exceptions - there is really no
 310  
         // point in wrapping them
 311  0
         if (ex instanceof RuntimeException) {
 312  0
             throw (RuntimeException) ex;
 313  
         }
 314  0
         if (ex instanceof Error) {
 315  0
             throw (Error) ex;
 316  
         }
 317  
 
 318  0
         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  0
         private int modCount = -1;
 337  
         private Set keySet;
 338  
         private int size;        
 339  
 
 340  0
         public EntrySet(int type) {
 341  0
             this.type = type;
 342  0
             update();            
 343  0
         }
 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  0
             if (modCount != ReflectedMap.this.modCount) {
 349  0
                 update();
 350  
             }
 351  0
         }
 352  
 
 353  
         public void update() {
 354  
             // Make sure modCount of EntrySet is maintained in sync with
 355  
             // that of the embracing List
 356  0
             modCount = ReflectedMap.this.modCount;
 357  
             
 358  0
             keySet = ReflectedMap.this.getPropertyKeySet();
 359  0
             size = keySet.size();
 360  0
         }
 361  
         
 362  
         public int size() {
 363  0
             refresh();
 364  0
             return size;
 365  
         }
 366  
         
 367  
         public Iterator iterator() {
 368  0
             refresh();
 369  0
             return new EntryIterator(keySet, type);
 370  
         }
 371  
         
 372  
         public boolean remove(Object object) {
 373  0
             refresh();
 374  0
             Object key =
 375  
                 (type == KEYS ? object : ((Map.Entry) object).getKey());
 376  
                 
 377  0
             boolean exists = true;
 378  
             try {
 379  0
                 exists = keySet.contains(key);
 380  
             }
 381  0
             catch (Throwable t) {
 382  
                 // Ignore
 383  0
             }
 384  0
             if (exists) {
 385  0
                 ReflectedMap.this.remove(key);
 386  
             }
 387  0
             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  0
         private Object lastReturned = UNINITIALIZED;
 400  
         
 401  0
         public EntryIterator(Set keySet, int type) {
 402  0
             this.type = type;
 403  0
             this.keySet = keySet;
 404  0
             this.keyIterator = keySet.iterator();
 405  0
         }
 406  
         
 407  
         /**
 408  
          * @see java.util.Iterator#hasNext()
 409  
          */
 410  
         public boolean hasNext() {
 411  0
             return keyIterator.hasNext();
 412  
         }
 413  
 
 414  
         /**
 415  
          * @see java.util.Iterator#next()
 416  
          */
 417  
         public Object next() {            
 418  0
             lastReturned = keyIterator.next();
 419  0
             if (type == KEYS) {
 420  0
                 return lastReturned;
 421  
             }
 422  
             else {
 423  0
                 return new Entry(lastReturned);
 424  
             }
 425  
         }
 426  
 
 427  
         /**
 428  
          * @see java.util.Iterator#remove()
 429  
          */
 430  
         public void remove() {
 431  0
             if (lastReturned == UNINITIALIZED) {
 432  0
                 throw new IllegalStateException();
 433  
             }
 434  0
             ensureConcurrentChangeSafety();
 435  0
             ReflectedMap.this.remove(lastReturned);
 436  0
         }
 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  0
             if (!(keySet instanceof ConcurrentChangeSafeSet)) {
 449  0
                 keySet = new ConcurrentChangeSafeSet(keySet);
 450  0
                 keyIterator = keySet.iterator(); 
 451  0
                 while (keyIterator.hasNext()) {
 452  0
                     Object key = keyIterator.next();
 453  0
                     if ((key == null && lastReturned == null)
 454  
                         || (key != null && key.equals(lastReturned))) {
 455  0
                         return;
 456  
                     }
 457  0
                 }
 458  0
                 throw new IllegalStateException(
 459  
                     "The second iteration over the key set"
 460  
                         + " did not produce the same elements");
 461  
             }
 462  0
         }
 463  
     }
 464  
     
 465  0
     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  0
         public Entry(Object key) {
 475  0
             this.key = key;            
 476  0
         }
 477  
         
 478  
         /**
 479  
          * @see java.util.Map.Entry#getKey()
 480  
          */
 481  
         public Object getKey() {
 482  0
             return key;
 483  
         }
 484  
         /**
 485  
          * @see java.util.Map.Entry#getValue()
 486  
          */
 487  
         public Object getValue() {
 488  0
             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  0
             return ReflectedMap.this.put(key, value);
 496  
         }        
 497  
 
 498  
         public boolean equals(Object o) {
 499  0
             if (!(o instanceof Map.Entry)) {
 500  0
                 return false;
 501  
             }
 502  
             
 503  0
             Map.Entry e = (Map.Entry) o;
 504  0
             return (key == null ? e.getKey() == null : key.equals(e.getKey()));
 505  
         }
 506  
 
 507  
         public int hashCode() {
 508  0
             return key == null ? 0 : key.hashCode();
 509  
         }
 510  
 
 511  
         public String toString() {
 512  0
             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  0
             super(collection);
 524  0
         }
 525  
     }
 526  
 }