Coverage Report - org.apache.commons.clazz.reflect.common.ReflectedList
 
Classes in this File Line Coverage Branch Coverage Complexity
ReflectedList
0%
0/175
0%
0/70
4.44
ReflectedList$QuickList
0%
0/31
0%
0/14
4.44
 
 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.common;
 17  
 
 18  
 import java.lang.reflect.Array;
 19  
 import java.lang.reflect.InvocationTargetException;
 20  
 import java.lang.reflect.Method;
 21  
 import java.util.AbstractList;
 22  
 import java.util.ArrayList;
 23  
 import java.util.Iterator;
 24  
 import java.util.List;
 25  
 import java.util.ListIterator;
 26  
 
 27  
 import org.apache.commons.clazz.ClazzAccessException;
 28  
 
 29  
 
 30  
 /**
 31  
  * This is an implementation of the <code>List</code> interface
 32  
  * that is based on a List property.  Whenever possible, it
 33  
  * uses concrete methods on the owner of the property to manipulate 
 34  
  * the list or array.
 35  
  * <p>
 36  
  * Consider the following example:
 37  
  * <pre>
 38  
  *      List list = (List)clazz.getProperty("fooList").get(instance);
 39  
  *      Object value = list.get(3);
 40  
  * </pre>
 41  
  * 
 42  
  * If <code>instance</code> has a <code>getFoo(int index)</code> method,
 43  
  * this code will implicitly invoke it like this: <code>getFoo(3)</code>,
 44  
  * otherwise it will obtain the whole list or array and extract the 
 45  
  * requested element.
 46  
  * 
 47  
  * @author <a href="mailto:dmitri@apache.org">Dmitri Plotnikov</a>
 48  
  * @version $Id: ReflectedList.java 155436 2005-02-26 13:17:48Z dirkv $
 49  
  */
 50  0
 public class ReflectedList extends AbstractList {
 51  
     /*
 52  
      * We have a concurrent modification issue with ReflectedList. We have no
 53  
      * way of knowing if somebody has modified the property value unless it was
 54  
      * modified through this very ReflectedList. So, in some cases we will not
 55  
      * get a ConcurrentModificationException when we are supposed to.
 56  
      */
 57  
 
 58  
     private Object instance;
 59  
     private ReflectedListProperty property;
 60  
 
 61  0
     private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
 62  
 
 63  
     /**
 64  
      * Constructor for ReflectedList.
 65  
      */
 66  0
     public ReflectedList(Object instance, ReflectedListProperty property) {
 67  0
         this.instance = instance;
 68  0
         this.property = property;
 69  0
     }
 70  
 
 71  
     public Object getPropertyValue() {
 72  0
         Method readMethod = property.getReadMethod();
 73  0
         if (readMethod == null) {
 74  0
             throw new ClazzAccessException(
 75  
                 "Cannot read property "
 76  
                     + property.getName()
 77  
                     + ": no read method");
 78  
         }
 79  
         try {
 80  0
             return readMethod.invoke(instance, null);
 81  
         }
 82  0
         catch (Exception ex) {
 83  0
             throw accessException("Cannot read property", readMethod, ex);
 84  
         }
 85  
     }
 86  
 
 87  
     public void setPropertyValue(Object value) {
 88  0
         Method writeMethod = property.getWriteMethod();
 89  0
         if (writeMethod == null) {
 90  0
             throw new ClazzAccessException(
 91  
                 "Cannot set property: "
 92  
                     + property.getName()
 93  
                     + ": no set(array) method");
 94  
         }
 95  
 
 96  
         try {
 97  0
             writeMethod.invoke(instance, new Object[] { value });
 98  
         }
 99  0
         catch (Exception ex) {
 100  0
             throw accessException("Cannot set property", writeMethod, ex);
 101  0
         }
 102  0
     }
 103  
 
 104  
     /**
 105  
      * @see java.util.Collection#size()
 106  
      */
 107  
     public int size() {
 108  0
         Method sizeMethod = property.getSizeMethod();
 109  0
         if (sizeMethod != null) {
 110  
             try {
 111  0
                 Object value = sizeMethod.invoke(instance, null);
 112  0
                 return ((Integer) value).intValue();
 113  
             }
 114  0
             catch (Exception ex) {
 115  0
                 throw accessException("Cannot get list size", sizeMethod, ex);
 116  
             }
 117  
         }
 118  
         else {
 119  0
             Object list = getPropertyValue();
 120  0
             if (list == null) {
 121  0
                 return 0;
 122  
             }
 123  
 
 124  0
             if (list instanceof List) {
 125  0
                 return ((List) list).size();
 126  
             }
 127  
 
 128  0
             return Array.getLength(list);
 129  
         }
 130  
     }
 131  
 
 132  
     /**
 133  
      * @see java.util.List#get(int)
 134  
      */
 135  
     public Object get(int index) {
 136  0
         Method getMethod = property.getGetMethod();
 137  0
         if (getMethod != null) {
 138  
             Object value;
 139  
             try {
 140  0
                 value =
 141  
                     getMethod.invoke(
 142  
                         instance,
 143  
                         new Object[] { new Integer(index)});
 144  
             }
 145  0
             catch (Throwable ex) {
 146  0
                 throw accessException("Cannot get property", getMethod, ex);
 147  0
             }
 148  0
             return value;
 149  
         }
 150  
         else {
 151  0
             Object list = getPropertyValue();
 152  0
             if (list == null) {
 153  0
                 return null;
 154  
             }
 155  
 
 156  0
             if (list instanceof List) {
 157  0
                 return ((List) list).get(index);
 158  
             }
 159  
 
 160  0
             return Array.get(list, index);
 161  
         }
 162  
     }
 163  
 
 164  
     /**
 165  
      * @see java.util.Collection#iterator()
 166  
      */
 167  
     public Iterator iterator() {
 168  0
         return new QuickList().iterator();
 169  
     }
 170  
 
 171  
     /**
 172  
      * @see java.util.List#listIterator()
 173  
      */
 174  
     public ListIterator listIterator() {
 175  0
         return new QuickList().listIterator();
 176  
     }
 177  
 
 178  
     /**
 179  
      * @see java.util.List#listIterator(int)
 180  
      */
 181  
     public ListIterator listIterator(int index) {
 182  0
         return new QuickList().listIterator(index);
 183  
     }
 184  
 
 185  
     /**
 186  
      * @see java.util.List#set(int, java.lang.Object)
 187  
      */
 188  
     public Object set(int index, Object element) {
 189  0
         modCount++;
 190  0
         Method setMethod = property.getSetMethod();
 191  0
         if (setMethod != null) {
 192  0
             Object oldValue = null;
 193  
             try {
 194  0
                 oldValue = get(index);
 195  
             }
 196  0
             catch (Throwable t) {
 197  
                 // Ignore
 198  0
             }
 199  
             try {
 200  0
                 setMethod.invoke(
 201  
                     instance,
 202  
                     new Object[] { new Integer(index), element });
 203  
             }
 204  0
             catch (Exception ex) {
 205  0
                 throw accessException(
 206  
                     "Cannot set property element",
 207  
                     setMethod,
 208  
                     ex);
 209  0
             }
 210  0
             return oldValue;
 211  
         }
 212  
         else {
 213  0
             Object list = getPropertyValue();
 214  0
             if (list == null) {
 215  0
                 return null;
 216  
             }
 217  
 
 218  0
             if (list instanceof List) {
 219  0
                 return ((List) list).set(index, element);
 220  
             }
 221  
 
 222  0
             Object oldValue = Array.get(list, index);
 223  0
             Array.set(list, index, element);
 224  0
             return oldValue;
 225  
         }
 226  
     }
 227  
 
 228  
     /**
 229  
      * Will perform the following steps:
 230  
      * <ol>
 231  
      * 
 232  
      * <li>If the instance has an <code>addFoo(element)</code>, calls that
 233  
      * method.</li>
 234  
      * 
 235  
      * <li>Otherwise, if the instance has an <code>add(index,element)</code>,
 236  
      * computes the size of the list and calls <code>add(size(),element)
 237  
      * </code>.<li>
 238  
      * 
 239  
      * <li>Othewise, if the instance has a <code>List getFoo<i>[plural suffix]
 240  
      * </i>()</code> method, calls that and adds the element to the list.</li>
 241  
      * 
 242  
      * <li>Othewise, if the instance has a <code>Foo[] getFoo<i>[plural suffix]
 243  
      * </i>()</code> method as well as a <code>setFoo<i>[plural suffix]
 244  
      * </i>(Foo[])</code> method, calls the read method, copies the array into a
 245  
      * new array with an additional element and calls the write method to assign
 246  
      * the new array to the property.</li>
 247  
      * 
 248  
      * </ol>
 249  
      * 
 250  
      * @see java.util.Collection#add(java.lang.Object)
 251  
      */
 252  
     public boolean add(Object element) {
 253  0
         modCount++;
 254  0
         if (property.getAddMethod() != null) {
 255  0
             Method addMethod = property.getAddMethod();
 256  
             try {
 257  0
                 addMethod.invoke(instance, new Object[] { element });
 258  
             }
 259  0
             catch (Exception ex) {
 260  0
                 throw accessException(
 261  
                     "Cannot add value to property",
 262  
                     addMethod,
 263  
                     ex);
 264  0
             }
 265  0
         }
 266  
         else {
 267  0
             add(-1, element);
 268  
         }
 269  0
         return true;
 270  
     }
 271  
 
 272  
     /**
 273  
      * Will perform the following steps:
 274  
      * <ol>
 275  
      * <li>If the instance has an <code>add(index,element)</code>,
 276  
      * calls that method.<li>
 277  
      * 
 278  
      * <li>Othewise, if the instance has an <code>add(element)</code> method and
 279  
      * index == size(), calls that method.</li>
 280  
      * 
 281  
      * <li>Othewise, if the instance has a <code>List getFoo<i> [plural
 282  
      * suffix]</i>()</code> method, calls that and inserts the element into the
 283  
      * list.</li>
 284  
      * 
 285  
      * <li>Othewise, if the instance has a <code>Foo[] getFoo<i>[plural suffix]
 286  
      * </i>()</code> method as well as a <code>setFoo<i>[plural suffix]
 287  
      * </i>(Foo[])</code> method, calls the read method, copies the array into a
 288  
      * new, one-longer, array inserting the additional element and calls the
 289  
      * write method to assign the new array to the property.</li>
 290  
      * 
 291  
      * </ol>
 292  
      * 
 293  
      * @see java.util.List#add(int, java.lang.Object)
 294  
      */
 295  
     public void add(int index, Object element) {
 296  0
         modCount++;
 297  0
         if (property.getAddIndexedMethod() != null) {
 298  0
             if (index == -1) { // This would indicate that the call
 299  
                 // is coming from add(element) and
 300  
                 // the addMethod does not exist
 301  0
                 index = size();
 302  
             }
 303  0
             Method addIndexedMethod = property.getAddIndexedMethod();
 304  
             try {
 305  0
                 addIndexedMethod.invoke(
 306  
                     instance,
 307  
                     new Object[] { new Integer(index), element });
 308  
             }
 309  0
             catch (Exception ex) {
 310  0
                 throw accessException(
 311  
                     "Cannot add value to property",
 312  
                     addIndexedMethod,
 313  
                     ex);
 314  0
             }
 315  0
             return;
 316  
         }
 317  
 
 318  0
         if (property.getAddMethod() != null && index == size()) { // This
 319  
             // guarantees that the call is not coming from add(element),
 320  
             // therefore we can call it without the fear of recursion
 321  0
             add(element);
 322  
         }
 323  0
         else if (property.getType().isArray()) {
 324  0
             addToArray(index, element);
 325  
         }
 326  
         else {
 327  0
             addToList(index, element);
 328  
         }
 329  0
     }
 330  
 
 331  
     /**
 332  
      * Inserts a new element into an array.  Creates the array if necessary. 
 333  
      */
 334  
     private void addToArray(int index, Object element) {
 335  
         Object newList;
 336  0
         Object list = getPropertyValue();
 337  0
         if (list == null) {
 338  0
             if (index != 0 && index != -1) {
 339  0
                 throw new ArrayIndexOutOfBoundsException(
 340  
                     "Size: 0; Index: " + index);
 341  
             }
 342  0
             Class contentType = property.getContentType();
 343  0
             newList = Array.newInstance(contentType, 1);
 344  0
             Array.set(newList, 0, element);
 345  0
         }
 346  
         else {
 347  0
             Class contentType = property.getContentType();
 348  0
             int size = Array.getLength(list);
 349  0
             if (index == -1) {
 350  0
                 index = size;
 351  
             }
 352  0
             if (index < 0 || index > size) {
 353  0
                 throw new ArrayIndexOutOfBoundsException(
 354  
                     "Size: " + size + "; Index: " + index);
 355  
             }
 356  0
             newList = Array.newInstance(contentType, size + 1);
 357  0
             System.arraycopy(list, 0, newList, 0, index);
 358  0
             Array.set(newList, index, element);
 359  0
             System.arraycopy(list, index, newList, index + 1, size - index);
 360  
         }
 361  
 
 362  0
         setPropertyValue(newList);
 363  0
     }
 364  
 
 365  
     /**
 366  
      * Inserts a new element into an List.  Creates the list if necessary.
 367  
      */
 368  
     private void addToList(int index, Object element) {
 369  0
         Object list = getPropertyValue();
 370  0
         if (list == null) {
 371  
             List newList;
 372  0
             Class type = property.getType();
 373  0
             if (!type.isInterface()) {
 374  
                 try {
 375  0
                     newList = (List) type.newInstance();
 376  
                 }
 377  0
                 catch (Exception ex) {
 378  0
                     throw new ClazzAccessException(
 379  
                         "Cannot add value to property : "
 380  
                             + property.getName()
 381  
                             + ": cannot create List of type "
 382  
                             + type.getName(),
 383  
                         ex);
 384  0
                 }
 385  
             }
 386  
             else {
 387  0
                 newList = new ArrayList();
 388  
             }
 389  0
             newList.add(element);
 390  
 
 391  0
             setPropertyValue(newList);
 392  0
         }
 393  0
         else if (index == -1) {
 394  0
             ((List) list).add(element);
 395  
         }
 396  
         else {
 397  0
             ((List) list).add(index, element);
 398  
         }
 399  0
     }
 400  
 
 401  
     /**
 402  
      * Will perform the following steps:
 403  
      * <ol>
 404  
      *
 405  
      * <li>If the instance has an <code>removeFoo(element)</code>, calls that
 406  
      * method.</li>
 407  
      *
 408  
      * <li>Otherwise, if iterates over elements of the collection until it
 409  
      * finds one equal to the supplied value. Then it removes it by calling
 410  
      * <code>remove(index)</code><li>
 411  
      *
 412  
      * </ol>
 413  
      *
 414  
      * @see java.util.Collection#remove(java.lang.Object)
 415  
      */
 416  
     public boolean remove(Object element) {
 417  0
         modCount++;
 418  0
         if (property.getRemoveMethod() != null) {
 419  0
             Method removeMethod = property.getRemoveMethod();
 420  
             try {
 421  0
                 removeMethod.invoke(instance, new Object[] { element });
 422  
             }
 423  0
             catch (Exception ex) {
 424  0
                 throw accessException(
 425  
                     "Cannot remove value from property",
 426  
                     removeMethod,
 427  
                     ex);
 428  0
             }
 429  0
             return true; // @todo: we really don't know if it got removed
 430  
         }
 431  
         else {
 432  0
             return super.remove(element);
 433  
         }
 434  
     }
 435  
 
 436  
     /**
 437  
      * Will perform the following steps:
 438  
      * <ol>
 439  
      * <li>If the instance has a <code>remove(index)</code>, calls that method.
 440  
      * <li>
 441  
      *
 442  
      * <li>Othewise, if the instance has an <code>add(element)</code> method and
 443  
      * index == size(), calls that method.</li>
 444  
      *
 445  
      * <li>Othewise, if the instance has a <code>List getFoo<i> [plural
 446  
      * suffix]</i>()</code> method, calls that and removes the element from the
 447  
      * list.
 448  
      * </li>
 449  
      *
 450  
      * <li>Othewise, if the instance has a <code>Foo[] getFoo<i>[plural suffix]
 451  
      * </i>()</code> method as well as a <code>setFoo<i>[plural suffix]
 452  
      * </i>(Foo[])</code> method, calls the read method, copies the array into a
 453  
      * new, one-shorter, array removing the supplied element and calls the write
 454  
      * method to assign the new array to the property.</li>
 455  
      *
 456  
      * </ol>
 457  
      *
 458  
      * @see java.util.List#add(int, java.lang.Object)
 459  
      */
 460  
     public Object remove(int index) {
 461  0
         modCount++;
 462  
 
 463  0
         if (property.getRemoveIndexedMethod() != null) {
 464  0
             Object value = null;
 465  
 
 466  
             try {
 467  0
                 value = get(index);
 468  
             }
 469  0
             catch (Throwable t) {
 470  
                 // Ignore
 471  0
             }
 472  
 
 473  0
             Method removeIndexedMethod = property.getRemoveIndexedMethod();
 474  
             try {
 475  0
                 removeIndexedMethod.invoke(
 476  
                     instance,
 477  
                     new Object[] { new Integer(index)});
 478  
             }
 479  0
             catch (Exception ex) {
 480  0
                 throw accessException(
 481  
                     "Cannot remove value from property",
 482  
                     removeIndexedMethod,
 483  
                     ex);
 484  0
             }
 485  
 
 486  0
             return value;
 487  
         }
 488  
 
 489  0
         Object list = getPropertyValue();
 490  0
         if (list == null) {
 491  0
             throw new ArrayIndexOutOfBoundsException(
 492  
                 "Size: 0; Index: " + index);
 493  
         }
 494  0
         else if (property.getType().isArray()) {
 495  
             Object value;
 496  0
             Class contentType = property.getContentType();
 497  0
             int size = Array.getLength(list);
 498  0
             if (index < 0 || index >= size) {
 499  0
                 throw new ArrayIndexOutOfBoundsException(
 500  
                     "Size: " + size + "; Index: " + index);
 501  
             }
 502  0
             value = Array.get(list, index);
 503  
 
 504  0
             Object newList = Array.newInstance(contentType, size - 1);
 505  0
             System.arraycopy(list, 0, newList, 0, index);
 506  0
             System.arraycopy(list, index + 1, newList, index, size - index - 1);
 507  0
             setPropertyValue(newList);
 508  0
             return value;
 509  
         }
 510  
         else {
 511  0
             return ((List) list).remove(index);
 512  
         }
 513  
     }
 514  
 
 515  
     private RuntimeException accessException(
 516  
         String message,
 517  
         Method method,
 518  
         Throwable ex) 
 519  
     {
 520  0
         if (ex instanceof InvocationTargetException) {
 521  0
             ex = ((InvocationTargetException) ex).getTargetException();
 522  
         }
 523  
 
 524  
         // Just re-throw all runtime exceptions - there is really no
 525  
         // point in wrapping them 
 526  0
         if (ex instanceof RuntimeException) {
 527  0
             throw (RuntimeException) ex;
 528  
         }
 529  0
         if (ex instanceof Error) {
 530  0
             throw (Error) ex;
 531  
         }
 532  
 
 533  0
         throw new ClazzAccessException(
 534  
             message
 535  
                 + ": "
 536  
                 + property.getName()
 537  
                 + ": cannot invoke method: "
 538  
                 + method.getName(),
 539  
             ex);
 540  
     }
 541  
 
 542  
     private int getModCount() {
 543  0
         return modCount;
 544  
     }
 545  
 
 546  
     /**
 547  
      * QuickList is used exclusively as short-lived helper object for
 548  
      * an optimization of Iterator.
 549  
      * 
 550  
      * The point is to avoid requesting the collection or its size from the
 551  
      * instance on every step of the iteration. The instance may be creating the
 552  
      * collection every time we ask for it, or it may be wrapping it into an
 553  
      * unmodifiable list. We want to avoid that overhead. The computation of
 554  
      * size can be rather expensive too.
 555  
      * 
 556  
      * A QuickList maintains temporary cache of the reflected collection and its
 557  
      * size.
 558  
      */
 559  
     private class QuickList extends AbstractList {
 560  
         private Object list;
 561  
         private int size;
 562  
 
 563  0
         public QuickList() {
 564  0
             update();
 565  0
         }
 566  
 
 567  
         public void refresh() {
 568  
             // If QuickList is out of sync with the parent ReflectedList,
 569  
             // update the cached list and size
 570  0
             if (super.modCount != getModCount()) {
 571  0
                 update();
 572  
             }
 573  0
         }
 574  
 
 575  
         public void update() {
 576  
             // Make sure modCount of QuickList is maintained in sync with
 577  
             // that of the embracing List 
 578  0
             super.modCount = getModCount();
 579  
 
 580  0
             if (property.getReadMethod() != null) {
 581  0
                 list = getPropertyValue();
 582  0
                 if (list == null) {
 583  0
                     size = 0;
 584  
                 }
 585  0
                 else if (list instanceof List) {
 586  0
                     size = ((List) list).size();
 587  
                 }
 588  
                 else {
 589  0
                     size = Array.getLength(list);
 590  
                 }
 591  
             }
 592  
             else {
 593  0
                 list = NOT_ACCESSIBLE;
 594  0
                 size = ReflectedList.this.size();
 595  
             }
 596  0
         }
 597  
 
 598  
         public int size() {
 599  0
             refresh();
 600  0
             return size;
 601  
         }
 602  
 
 603  
         public Object get(int index) {
 604  0
             refresh();
 605  0
             if (list != NOT_ACCESSIBLE) {
 606  0
                 if (list == null) {
 607  0
                     throw new IndexOutOfBoundsException(
 608  
                         "Size= 0; index=" + index);
 609  
                 }
 610  
 
 611  0
                 if (list instanceof List) {
 612  0
                     return ((List) list).get(index);
 613  
                 }
 614  
 
 615  0
                 return Array.get(list, index);
 616  
             }
 617  
             else {
 618  0
                 return ReflectedList.this.get(index);
 619  
             }
 620  
         }
 621  
 
 622  
         public Object set(int index, Object value) {
 623  0
             return ReflectedList.this.set(index, value);
 624  
         }
 625  
 
 626  
         public void add(int index, Object value) {
 627  0
             ReflectedList.this.add(index, value);
 628  0
         }
 629  
 
 630  
         public Object remove(int index) {
 631  0
             return ReflectedList.this.remove(index);
 632  
         }
 633  
 
 634  
     }
 635  
 
 636  0
     private static final Object NOT_ACCESSIBLE = new Object();
 637  
 }