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.common; 017 018 import java.lang.reflect.Array; 019 import java.lang.reflect.InvocationTargetException; 020 import java.lang.reflect.Method; 021 import java.util.AbstractList; 022 import java.util.ArrayList; 023 import java.util.Iterator; 024 import java.util.List; 025 import java.util.ListIterator; 026 027 import org.apache.commons.clazz.ClazzAccessException; 028 029 030 /** 031 * This is an implementation of the <code>List</code> interface 032 * that is based on a List property. Whenever possible, it 033 * uses concrete methods on the owner of the property to manipulate 034 * the list or array. 035 * <p> 036 * Consider the following example: 037 * <pre> 038 * List list = (List)clazz.getProperty("fooList").get(instance); 039 * Object value = list.get(3); 040 * </pre> 041 * 042 * If <code>instance</code> has a <code>getFoo(int index)</code> method, 043 * this code will implicitly invoke it like this: <code>getFoo(3)</code>, 044 * otherwise it will obtain the whole list or array and extract the 045 * requested element. 046 * 047 * @author <a href="mailto:dmitri@apache.org">Dmitri Plotnikov</a> 048 * @version $Id: ReflectedList.java 155436 2005-02-26 13:17:48Z dirkv $ 049 */ 050 public class ReflectedList extends AbstractList { 051 /* 052 * We have a concurrent modification issue with ReflectedList. We have no 053 * way of knowing if somebody has modified the property value unless it was 054 * modified through this very ReflectedList. So, in some cases we will not 055 * get a ConcurrentModificationException when we are supposed to. 056 */ 057 058 private Object instance; 059 private ReflectedListProperty property; 060 061 private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; 062 063 /** 064 * Constructor for ReflectedList. 065 */ 066 public ReflectedList(Object instance, ReflectedListProperty property) { 067 this.instance = instance; 068 this.property = property; 069 } 070 071 public Object getPropertyValue() { 072 Method readMethod = property.getReadMethod(); 073 if (readMethod == null) { 074 throw new ClazzAccessException( 075 "Cannot read property " 076 + property.getName() 077 + ": no read method"); 078 } 079 try { 080 return readMethod.invoke(instance, null); 081 } 082 catch (Exception ex) { 083 throw accessException("Cannot read property", readMethod, ex); 084 } 085 } 086 087 public void setPropertyValue(Object value) { 088 Method writeMethod = property.getWriteMethod(); 089 if (writeMethod == null) { 090 throw new ClazzAccessException( 091 "Cannot set property: " 092 + property.getName() 093 + ": no set(array) method"); 094 } 095 096 try { 097 writeMethod.invoke(instance, new Object[] { value }); 098 } 099 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 }