View Javadoc

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  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      private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
62  
63      /**
64       * Constructor for ReflectedList.
65       */
66      public ReflectedList(Object instance, ReflectedListProperty property) {
67          this.instance = instance;
68          this.property = property;
69      }
70  
71      public Object getPropertyValue() {
72          Method readMethod = property.getReadMethod();
73          if (readMethod == null) {
74              throw new ClazzAccessException(
75                  "Cannot read property "
76                      + property.getName()
77                      + ": no read method");
78          }
79          try {
80              return readMethod.invoke(instance, null);
81          }
82          catch (Exception ex) {
83              throw accessException("Cannot read property", readMethod, ex);
84          }
85      }
86  
87      public void setPropertyValue(Object value) {
88          Method writeMethod = property.getWriteMethod();
89          if (writeMethod == null) {
90              throw new ClazzAccessException(
91                  "Cannot set property: "
92                      + property.getName()
93                      + ": no set(array) method");
94          }
95  
96          try {
97              writeMethod.invoke(instance, new Object[] { value });
98          }
99          catch (Exception ex) {
100             throw accessException("Cannot set property", writeMethod, ex);
101         }
102     }
103 
104     /**
105      * @see java.util.Collection#size()
106      */
107     public int size() {
108         Method sizeMethod = property.getSizeMethod();
109         if (sizeMethod != null) {
110             try {
111                 Object value = sizeMethod.invoke(instance, null);
112                 return ((Integer) value).intValue();
113             }
114             catch (Exception ex) {
115                 throw accessException("Cannot get list size", sizeMethod, ex);
116             }
117         }
118         else {
119             Object list = getPropertyValue();
120             if (list == null) {
121                 return 0;
122             }
123 
124             if (list instanceof List) {
125                 return ((List) list).size();
126             }
127 
128             return Array.getLength(list);
129         }
130     }
131 
132     /**
133      * @see java.util.List#get(int)
134      */
135     public Object get(int index) {
136         Method getMethod = property.getGetMethod();
137         if (getMethod != null) {
138             Object value;
139             try {
140                 value =
141                     getMethod.invoke(
142                         instance,
143                         new Object[] { new Integer(index)});
144             }
145             catch (Throwable ex) {
146                 throw accessException("Cannot get property", getMethod, ex);
147             }
148             return value;
149         }
150         else {
151             Object list = getPropertyValue();
152             if (list == null) {
153                 return null;
154             }
155 
156             if (list instanceof List) {
157                 return ((List) list).get(index);
158             }
159 
160             return Array.get(list, index);
161         }
162     }
163 
164     /**
165      * @see java.util.Collection#iterator()
166      */
167     public Iterator iterator() {
168         return new QuickList().iterator();
169     }
170 
171     /**
172      * @see java.util.List#listIterator()
173      */
174     public ListIterator listIterator() {
175         return new QuickList().listIterator();
176     }
177 
178     /**
179      * @see java.util.List#listIterator(int)
180      */
181     public ListIterator listIterator(int index) {
182         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         modCount++;
190         Method setMethod = property.getSetMethod();
191         if (setMethod != null) {
192             Object oldValue = null;
193             try {
194                 oldValue = get(index);
195             }
196             catch (Throwable t) {
197                 // Ignore
198             }
199             try {
200                 setMethod.invoke(
201                     instance,
202                     new Object[] { new Integer(index), element });
203             }
204             catch (Exception ex) {
205                 throw accessException(
206                     "Cannot set property element",
207                     setMethod,
208                     ex);
209             }
210             return oldValue;
211         }
212         else {
213             Object list = getPropertyValue();
214             if (list == null) {
215                 return null;
216             }
217 
218             if (list instanceof List) {
219                 return ((List) list).set(index, element);
220             }
221 
222             Object oldValue = Array.get(list, index);
223             Array.set(list, index, element);
224             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         modCount++;
254         if (property.getAddMethod() != null) {
255             Method addMethod = property.getAddMethod();
256             try {
257                 addMethod.invoke(instance, new Object[] { element });
258             }
259             catch (Exception ex) {
260                 throw accessException(
261                     "Cannot add value to property",
262                     addMethod,
263                     ex);
264             }
265         }
266         else {
267             add(-1, element);
268         }
269         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         modCount++;
297         if (property.getAddIndexedMethod() != null) {
298             if (index == -1) { // This would indicate that the call
299                 // is coming from add(element) and
300                 // the addMethod does not exist
301                 index = size();
302             }
303             Method addIndexedMethod = property.getAddIndexedMethod();
304             try {
305                 addIndexedMethod.invoke(
306                     instance,
307                     new Object[] { new Integer(index), element });
308             }
309             catch (Exception ex) {
310                 throw accessException(
311                     "Cannot add value to property",
312                     addIndexedMethod,
313                     ex);
314             }
315             return;
316         }
317 
318         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             add(element);
322         }
323         else if (property.getType().isArray()) {
324             addToArray(index, element);
325         }
326         else {
327             addToList(index, element);
328         }
329     }
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         Object list = getPropertyValue();
337         if (list == null) {
338             if (index != 0 && index != -1) {
339                 throw new ArrayIndexOutOfBoundsException(
340                     "Size: 0; Index: " + index);
341             }
342             Class contentType = property.getContentType();
343             newList = Array.newInstance(contentType, 1);
344             Array.set(newList, 0, element);
345         }
346         else {
347             Class contentType = property.getContentType();
348             int size = Array.getLength(list);
349             if (index == -1) {
350                 index = size;
351             }
352             if (index < 0 || index > size) {
353                 throw new ArrayIndexOutOfBoundsException(
354                     "Size: " + size + "; Index: " + index);
355             }
356             newList = Array.newInstance(contentType, size + 1);
357             System.arraycopy(list, 0, newList, 0, index);
358             Array.set(newList, index, element);
359             System.arraycopy(list, index, newList, index + 1, size - index);
360         }
361 
362         setPropertyValue(newList);
363     }
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         Object list = getPropertyValue();
370         if (list == null) {
371             List newList;
372             Class type = property.getType();
373             if (!type.isInterface()) {
374                 try {
375                     newList = (List) type.newInstance();
376                 }
377                 catch (Exception ex) {
378                     throw new ClazzAccessException(
379                         "Cannot add value to property : "
380                             + property.getName()
381                             + ": cannot create List of type "
382                             + type.getName(),
383                         ex);
384                 }
385             }
386             else {
387                 newList = new ArrayList();
388             }
389             newList.add(element);
390 
391             setPropertyValue(newList);
392         }
393         else if (index == -1) {
394             ((List) list).add(element);
395         }
396         else {
397             ((List) list).add(index, element);
398         }
399     }
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         modCount++;
418         if (property.getRemoveMethod() != null) {
419             Method removeMethod = property.getRemoveMethod();
420             try {
421                 removeMethod.invoke(instance, new Object[] { element });
422             }
423             catch (Exception ex) {
424                 throw accessException(
425                     "Cannot remove value from property",
426                     removeMethod,
427                     ex);
428             }
429             return true; // @todo: we really don't know if it got removed
430         }
431         else {
432             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         modCount++;
462 
463         if (property.getRemoveIndexedMethod() != null) {
464             Object value = null;
465 
466             try {
467                 value = get(index);
468             }
469             catch (Throwable t) {
470                 // Ignore
471             }
472 
473             Method removeIndexedMethod = property.getRemoveIndexedMethod();
474             try {
475                 removeIndexedMethod.invoke(
476                     instance,
477                     new Object[] { new Integer(index)});
478             }
479             catch (Exception ex) {
480                 throw accessException(
481                     "Cannot remove value from property",
482                     removeIndexedMethod,
483                     ex);
484             }
485 
486             return value;
487         }
488 
489         Object list = getPropertyValue();
490         if (list == null) {
491             throw new ArrayIndexOutOfBoundsException(
492                 "Size: 0; Index: " + index);
493         }
494         else if (property.getType().isArray()) {
495             Object value;
496             Class contentType = property.getContentType();
497             int size = Array.getLength(list);
498             if (index < 0 || index >= size) {
499                 throw new ArrayIndexOutOfBoundsException(
500                     "Size: " + size + "; Index: " + index);
501             }
502             value = Array.get(list, index);
503 
504             Object newList = Array.newInstance(contentType, size - 1);
505             System.arraycopy(list, 0, newList, 0, index);
506             System.arraycopy(list, index + 1, newList, index, size - index - 1);
507             setPropertyValue(newList);
508             return value;
509         }
510         else {
511             return ((List) list).remove(index);
512         }
513     }
514 
515     private RuntimeException accessException(
516         String message,
517         Method method,
518         Throwable ex) 
519     {
520         if (ex instanceof InvocationTargetException) {
521             ex = ((InvocationTargetException) ex).getTargetException();
522         }
523 
524         // Just re-throw all runtime exceptions - there is really no
525         // point in wrapping them 
526         if (ex instanceof RuntimeException) {
527             throw (RuntimeException) ex;
528         }
529         if (ex instanceof Error) {
530             throw (Error) ex;
531         }
532 
533         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         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         public QuickList() {
564             update();
565         }
566 
567         public void refresh() {
568             // If QuickList is out of sync with the parent ReflectedList,
569             // update the cached list and size
570             if (super.modCount != getModCount()) {
571                 update();
572             }
573         }
574 
575         public void update() {
576             // Make sure modCount of QuickList is maintained in sync with
577             // that of the embracing List 
578             super.modCount = getModCount();
579 
580             if (property.getReadMethod() != null) {
581                 list = getPropertyValue();
582                 if (list == null) {
583                     size = 0;
584                 }
585                 else if (list instanceof List) {
586                     size = ((List) list).size();
587                 }
588                 else {
589                     size = Array.getLength(list);
590                 }
591             }
592             else {
593                 list = NOT_ACCESSIBLE;
594                 size = ReflectedList.this.size();
595             }
596         }
597 
598         public int size() {
599             refresh();
600             return size;
601         }
602 
603         public Object get(int index) {
604             refresh();
605             if (list != NOT_ACCESSIBLE) {
606                 if (list == null) {
607                     throw new IndexOutOfBoundsException(
608                         "Size= 0; index=" + index);
609                 }
610 
611                 if (list instanceof List) {
612                     return ((List) list).get(index);
613                 }
614 
615                 return Array.get(list, index);
616             }
617             else {
618                 return ReflectedList.this.get(index);
619             }
620         }
621 
622         public Object set(int index, Object value) {
623             return ReflectedList.this.set(index, value);
624         }
625 
626         public void add(int index, Object value) {
627             ReflectedList.this.add(index, value);
628         }
629 
630         public Object remove(int index) {
631             return ReflectedList.this.remove(index);
632         }
633 
634     }
635 
636     private static final Object NOT_ACCESSIBLE = new Object();
637 }