001 /* 002 Copyright (C) 2001-2003 Renaud Pawlak <renaud@aopsys.com>, 003 Laurent Martelli <laurent@aopsys.com> 004 005 This program is free software; you can redistribute it and/or modify 006 it under the terms of the GNU Lesser General Public License as 007 published by the Free Software Foundation; either version 2 of the 008 License, or (at your option) any later version. 009 010 This program is distributed in the hope that it will be useful, 011 but WITHOUT ANY WARRANTY; without even the implied warranty of 012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 GNU Lesser General Public License for more details. 014 015 You should have received a copy of the GNU Lesser General Public License 016 along with this program; if not, write to the Free Software 017 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ 018 019 package org.objectweb.jac.core.rtti; 020 021 import java.lang.NoSuchMethodException; 022 import java.lang.reflect.*; 023 import java.lang.reflect.Modifier; 024 import java.util.ArrayList; 025 import java.util.Collection; 026 import java.util.HashSet; 027 import java.util.Iterator; 028 import java.util.List; 029 import java.util.Vector; 030 import org.apache.log4j.Logger; 031 import org.objectweb.jac.util.*; 032 033 /** 034 * This class defines a meta item that corresponds to the 035 * <code>java.lang.reflect.Field</code> meta element.<p> 036 * 037 * <p>In addition to the <code>java.lang.reflect</code> classical 038 * features, this RTTI method element is able to tell if a field is 039 * accessed for reading or writting by a given method. 040 * 041 * <p>It also provides some modification methods that are aspect compliant. 042 * 043 * <p>For the moment, default meta informations are setted by the 044 * <code>ClassRepository</code> class using some naming 045 * conventions. In a close future, these informations will be deduced 046 * from the class bytecodes analysis at load-time. 047 * 048 * @see java.lang.reflect.Field 049 * @see #getWritingMethods() 050 * @see #getAccessingMethods() 051 * 052 * @author Renaud Pawlak 053 * @author Laurent Martelli 054 */ 055 056 public class FieldItem extends MemberItem { 057 058 static Class wrappeeClass = ClassRepository.wrappeeClass; 059 static Logger logger = Logger.getLogger("rtti.field"); 060 061 /** 062 * Transforms a field items array into a fields array containing 063 * the <code>java.lang.reflect</code> fields wrapped by the method 064 * items.<p> 065 * 066 * @param fieldItems the field items 067 * @return the actual fields in <code>java.lang.reflect</code> 068 */ 069 070 public static Field[] toFields(FieldItem[] fieldItems) { 071 Field[] res = new Field[fieldItems.length]; 072 for (int i=0; i<fieldItems.length; i++) { 073 if (fieldItems[i] == null) { 074 res[i] = null; 075 } else { 076 res[i] = fieldItems[i].getActualField(); 077 } 078 } 079 return res; 080 } 081 082 /** 083 * Default contructor to create a new field item object.<p> 084 * 085 * @param delegate the <code>java.lang.reflect.Field</code> actual 086 * meta item */ 087 088 public FieldItem(Field delegate, ClassItem parent) 089 throws InvalidDelegateException 090 { 091 super(delegate,parent); 092 name = delegate.getName(); 093 } 094 095 public FieldItem(ClassItem parent) { 096 super(parent); 097 } 098 099 /** 100 * Creates a calculated FieldItem 101 * @param name name of the field 102 * @param getter the getter method of the field 103 */ 104 public FieldItem(String name, MethodItem getter, ClassItem parent) { 105 super(parent); 106 isCalculated = true; 107 this.name = name; 108 addAccessingMethod(getter); 109 setGetter(getter); 110 getter.addAccessedField(this); 111 getter.setReturnedField(this); 112 } 113 114 /** 115 * Creates a FieldItem with specific getter and setter 116 * @param name name of the field 117 * @param getter the getter method of the field 118 * @param setter the setter method of the field 119 */ 120 public FieldItem(String name, MethodItem getter, MethodItem setter, 121 ClassItem parent) 122 { 123 super(parent); 124 this.name = name; 125 setGetter(getter); 126 setSetter(setter); 127 } 128 129 /** 130 * Creates an expression FieldItem 131 * @param expression expression of the field 132 */ 133 public FieldItem(String expression, List path, ClassItem parent) { 134 super(parent); 135 isCalculated = true; 136 isExpression = true; 137 this.name = expression; 138 this.path = path; 139 type = getPathTop().getType(); 140 } 141 142 boolean isCalculated = false; 143 boolean isExpression = false; 144 String name; 145 Class type; 146 147 // Used if isExpression==true FieldItem[] 148 List path; 149 150 public FieldItem getPathTop() { 151 return (FieldItem)path.get(path.size()-1); 152 } 153 154 /** 155 * If the field does not have a value for the request attribute, 156 * tries on the superclass. 157 */ 158 public final Object getAttribute(String name) { 159 Object value = super.getAttribute(name); 160 if (value==null) { 161 ClassItem parent = ((ClassItem)getParent()).getSuperclass(); 162 if (parent!=null) { 163 if (parent.hasField(getName())) { 164 value = parent.getField(this.name).getAttribute(name); 165 } 166 } 167 } 168 if (isExpression && value==null) { 169 return ((FieldItem)path.get(path.size()-1)).getAttribute(name); 170 } 171 return value; 172 } 173 174 175 MethodItem[] accessingMethods; 176 177 /** 178 * Get the methods that access this field for reading.<p> 179 * 180 * @return value of accessingMethods. 181 */ 182 public final MethodItem[] getAccessingMethods() { 183 ((ClassItem)parent).buildFieldInfo(); 184 return accessingMethods; 185 } 186 187 public final boolean hasAccessingMethods() { 188 ((ClassItem)parent).buildFieldInfo(); 189 return accessingMethods!=null && accessingMethods.length>0; 190 } 191 192 /** 193 * Set the methods that access this field for reading.<p> 194 * 195 * @param accessingMethods value to assign to accessingMethods. 196 */ 197 198 public final void setAccessingMethods(MethodItem[] accessingMethods) { 199 this.accessingMethods = accessingMethods; 200 } 201 202 /** 203 * Add a new accessing method for this field.<p> 204 * 205 * @param accessingMethod the method to add 206 */ 207 208 public final void addAccessingMethod(MethodItem accessingMethod) { 209 if (accessingMethods == null) { 210 accessingMethods = new MethodItem[] { accessingMethod }; 211 } else { 212 MethodItem[] tmp = new MethodItem[accessingMethods.length + 1]; 213 System.arraycopy(accessingMethods, 0, tmp, 0, accessingMethods.length); 214 tmp[accessingMethods.length] = accessingMethod; 215 accessingMethods = tmp; 216 } 217 } 218 219 MethodItem[] writingMethods; 220 221 /** 222 * Get the methods that access this field for writing.<p> 223 * 224 * @return value of writingMethods. 225 */ 226 public final MethodItem[] getWritingMethods() { 227 ((ClassItem)parent).buildFieldInfo(); 228 return writingMethods; 229 } 230 public final boolean hasWritingMethods() { 231 ((ClassItem)parent).buildFieldInfo(); 232 return writingMethods!=null && writingMethods.length>0; 233 } 234 235 /** 236 * Set the methods that access this field for writing.<p> 237 * 238 * @param writingMethods value to assign to writingMethods. 239 */ 240 241 public final void setWritingMethods(MethodItem[] writingMethods) { 242 this.writingMethods = writingMethods; 243 } 244 245 /** 246 * Add a new writing method for this field.<p> 247 * 248 * @param writingMethod the method to add 249 */ 250 251 public final void addWritingMethod(MethodItem writingMethod) { 252 if (writingMethods == null) { 253 writingMethods = new MethodItem[] { writingMethod }; 254 } else { 255 MethodItem[] tmp = new MethodItem[writingMethods.length + 1]; 256 System.arraycopy(writingMethods, 0, tmp, 0, writingMethods.length); 257 tmp[writingMethods.length] = writingMethod; 258 writingMethods = tmp; 259 } 260 } 261 262 /** 263 * Remove accessing and writing methods 264 */ 265 public void clearMethods() { 266 if (writingMethods != null) { 267 for (int i=0; i<writingMethods.length; i++) { 268 writingMethods[i].removeWrittenField(this); 269 } 270 writingMethods = null; 271 } 272 if (accessingMethods != null) { 273 for (int i=0; i<accessingMethods.length; i++) { 274 accessingMethods[i].removeAccessedField(this); 275 } 276 accessingMethods = null; 277 } 278 } 279 280 FieldItem[] dependentFields = FieldItem.emptyArray; 281 /** 282 * @see #getDependentFields() 283 */ 284 public final void addDependentField(FieldItem field) { 285 FieldItem[] tmp = new FieldItem[dependentFields.length+1]; 286 System.arraycopy(dependentFields, 0, tmp, 0, dependentFields.length); 287 tmp[dependentFields.length] = field; 288 dependentFields = tmp; 289 ClassItem superClass = getClassItem().getSuperclass(); 290 if (superClass!=null) { 291 FieldItem superField = superClass.getFieldNoError(getName()); 292 if (superField!=null) 293 superField.addDependentField(field); 294 } 295 } 296 /** 297 * Returns an array of calculated fields which depend on the field. 298 * @see #addDependentField(FieldItem) 299 */ 300 public final FieldItem[] getDependentFields() { 301 return dependentFields; 302 } 303 304 /** 305 * Get the field represented by this field item.<p> 306 * 307 * @return the actual field 308 */ 309 310 public final Field getActualField() { 311 return (Field)delegate; 312 } 313 314 /** 315 * Returns proper substance to invoke methods on for expression 316 * fields. 317 */ 318 public Object getSubstance(Object substance) { 319 if (isExpression) { 320 Iterator it = path.iterator(); 321 while (it.hasNext() && substance!=null) { 322 FieldItem field = (FieldItem)it.next(); 323 if (!it.hasNext()) { 324 return substance; 325 } 326 substance = field.getThroughAccessor(substance); 327 } 328 return null; 329 } else { 330 return substance; 331 } 332 } 333 334 /** 335 * Returns the substances list for expression fields. 336 * 337 * <p>Since expression fields can contain collections, there can be 338 * multiple substances related to them. 339 * @param substance 340 * @return a list (can be empty but never null) 341 */ 342 public List getSubstances(Object substance) { 343 Vector substances = new Vector(); 344 substances.add(substance); 345 if (isExpression) { 346 Iterator it = path.iterator(); 347 while (it.hasNext() && substance!=null) { 348 FieldItem field = (FieldItem)it.next(); 349 if (!it.hasNext()) { 350 break; 351 } 352 Vector current = substances; 353 substances = new Vector(current.size()); 354 for(Iterator j = current.iterator(); j.hasNext();) { 355 Object o = j.next(); 356 if (field instanceof CollectionItem) { 357 substances.addAll( 358 ((CollectionItem)field).getActualCollectionThroughAccessor(o)); 359 } else { 360 substances.add(field.getThroughAccessor(o)); 361 } 362 } 363 } 364 } 365 return substances; 366 } 367 368 /** 369 * Returns the actual field. In the case of an expression field, 370 * this is the last element of the path, otherwise it is the field 371 * itself. 372 */ 373 public FieldItem getField() { 374 if (isExpression) { 375 return getPathTop(); 376 } else { 377 return this; 378 } 379 } 380 381 /** 382 * Get the value of this field item for a given object.<p> 383 * 384 * @param object the object that supports the field 385 * @return the field value in the given object 386 * @see #set(Object,Object) 387 */ 388 public final Object get(Object object) { 389 Object ret = null; 390 try { 391 ret = ((Field)delegate).get(object); 392 } catch (Exception e) { 393 logger.error("Failed to get value of field "+this+" for "+object+ 394 (object!=null?(" ("+object.getClass().getName()+")"):""), 395 e); 396 } 397 return ret; 398 } 399 400 /** 401 * Get a field value through accessor if it exists.<p> 402 * 403 * @param substance the object that supports the field 404 */ 405 public Object getThroughAccessor(Object substance) 406 { 407 ((ClassItem)parent).buildFieldInfo(); 408 if (isExpression) { 409 Iterator it = path.iterator(); 410 while (it.hasNext() && substance!=null) { 411 FieldItem field = (FieldItem)it.next(); 412 substance = field.getThroughAccessor(substance); 413 } 414 return substance; 415 } else { 416 Object value = null; 417 String name; 418 MethodItem getter = getGetter(); 419 if (getter!=null) { 420 //Log.trace("rtti.field",this+": invoking "+accessors[i]); 421 return (getter.invoke(substance,ExtArrays.emptyObjectArray)); 422 } else { 423 logger.warn("No accessor found for field " + this); 424 return get(substance); 425 } 426 } 427 } 428 429 /** 430 * Gets the leaves of an object path. 431 * 432 * <p>If path is not an expression field, returns 433 * <code>getActualCollectionThroughAccessor()</code> if it's a 434 * CollectionItem or a CollectionItem containing 435 * <code>getThroughAccessor()</code> otherwise. If path is an 436 * expression field, getPathLeaves() is called recursively on all 437 * the component fields of the expression field.</p> 438 * 439 * @param path the path 440 * @param root the root object the path will be applied to. 441 */ 442 public static Collection getPathLeaves(FieldItem path, Object root) { 443 ((ClassItem)path.parent).buildFieldInfo(); 444 445 if (path.isExpression) { 446 HashSet currentSet = new HashSet(); 447 currentSet.add(root); 448 Iterator it = path.path.iterator(); 449 while (it.hasNext()) { 450 FieldItem field = (FieldItem)it.next(); 451 HashSet newSet = new HashSet(); 452 Iterator j = currentSet.iterator(); 453 while(j.hasNext()) { 454 Object o = j.next(); 455 newSet.addAll(getPathLeaves(field,o)); 456 } 457 currentSet = newSet; 458 } 459 return currentSet; 460 } else { 461 if (path instanceof CollectionItem) 462 return ((CollectionItem)path).getActualCollectionThroughAccessor(root); 463 else { 464 ArrayList singleton = new ArrayList(1); 465 singleton.add(path.getThroughAccessor(root)); 466 return singleton; 467 } 468 } 469 } 470 471 /** 472 * Sets the value of this field item for a given object.<p> 473 * 474 * @param object the object whose field must be set 475 * @param value the value to set 476 * @see #get(Object) 477 * @see #setConvert(Object,Object) 478 */ 479 public final void set(Object object, Object value) 480 throws IllegalAccessException, IllegalArgumentException 481 { 482 if (value==null && getType().isPrimitive()) { 483 logger.error("Cannot set primitive field "+this+" to null", new Exception()); 484 } else { 485 ((Field)delegate).set(object, value); 486 } 487 } 488 489 /** 490 * Sets the value of this field item for a given object. If the 491 * value is not assignable to the field, tries to convert it.<p> 492 * 493 * <p>It can convert floats and doubles to int or long, and 494 * anything to String.</p> 495 * 496 * @param object the object whose field must be set 497 * @param value the value to set. Must not be null. 498 * @return true if value had to be converted, false otherwise 499 * 500 * @see #set(Object,Object) */ 501 public final boolean setConvert(Object object, Object value) 502 throws IllegalAccessException, IllegalArgumentException, 503 InstantiationException, InvocationTargetException, NoSuchMethodException 504 { 505 try { 506 set(object,value); 507 return false; 508 } catch(IllegalArgumentException e) { 509 Object convertedValue = RttiAC.convert(value,getType()); 510 set(object,convertedValue); 511 return true; 512 } 513 } 514 515 /** 516 * Sets the value of this field item by using its setter method if 517 * any (else use the <code>set</code> method. 518 * 519 * @param substance the object to set the field of 520 * @param value the new value of the field 521 * @see #set(Object,Object) 522 */ 523 public final void setThroughWriter(Object substance, Object value) 524 throws IllegalAccessException, IllegalArgumentException 525 { 526 ((ClassItem)parent).buildFieldInfo(); 527 if (isExpression) { 528 Iterator it = path.iterator(); 529 while (it.hasNext()) { 530 FieldItem field = (FieldItem)it.next(); 531 if (it.hasNext()) { 532 substance = field.getThroughAccessor(substance); 533 } else { 534 field.setThroughWriter(substance,value); 535 return; 536 } 537 } 538 } else { 539 logger.debug("setThroughWriter "+substance+"."+getName()+","+value); 540 String name; 541 if (setter!=null) { 542 try { 543 logger.debug(this+": invoking "+setter); 544 setter.invoke(substance,new Object[] { value }); 545 return; 546 } catch (WrappedThrowableException e) { 547 Throwable target = e.getWrappedThrowable(); 548 if (target instanceof IllegalArgumentException) 549 logger.error("setThroughWriter: IllegalArgumentException for "+substance+"."+getName()+ 550 " = "+Strings.hex(value)+"("+value+")"); 551 throw e; 552 } 553 } 554 } 555 556 if (isCalculated()) { 557 throw new RuntimeException("Cannot set calculted field "+getLongName()); 558 } 559 logger.warn("No setter found for field "+this); 560 set(substance,value); 561 } 562 563 /* <em>the</em> setter of the field */ 564 MethodItem setter; 565 566 public MethodItem getSetter() { 567 ((ClassItem)parent).buildFieldInfo(); 568 if (setter!=null) 569 return setter; 570 else if (isExpression) 571 return getPathTop().getSetter(); 572 else 573 return null; 574 } 575 576 public void setSetter(MethodItem setter) { 577 if (this.setter!=null) { 578 logger.warn("overriding setter "+ 579 this.setter.getFullName()+" for field "+ 580 this+" with "+setter.getFullName()); 581 } 582 this.setter = setter; 583 addWritingMethod(setter); 584 } 585 586 /* <em>the</em> getter of the field */ 587 MethodItem getter; 588 /** 589 * Returns <em>the</em> getter of the field, if any. 590 */ 591 public MethodItem getGetter() { 592 if (!isExpression) 593 ((ClassItem)parent).buildFieldInfo(); 594 return getter; 595 } 596 597 public void setGetter(MethodItem getter) { 598 ((ClassItem)parent).buildFieldInfo(); 599 if (this.getter!=null) { 600 if (getType().isAssignableFrom(getter.getType())) { 601 setType(getter.getType()); 602 } else { 603 logger.warn("overriding getter "+ 604 this.getter.getFullName()+ 605 " for field "+this+" with "+getter.getFullName()); 606 } 607 } 608 if (!isCalculated && getType()!=getter.getType() && 609 getType().isAssignableFrom(getter.getType())) { 610 setType(getter.getType()); 611 } 612 this.getter = getter; 613 addAccessingMethod(getter); 614 } 615 616 public String getName() { 617 return name; 618 } 619 620 public Class getType() { 621 if (type!=null) 622 return type; 623 else if (getter!=null) 624 return getter.getType(); 625 else 626 return ((Field)delegate).getType(); 627 } 628 629 public void setType(Class type) { 630 logger.info("overriding field type of "+this+ 631 " with "+type.getName()); 632 this.type = type; 633 } 634 635 /** 636 * Tells if this field item represents a primitive type (that is to 637 * say it is of a type that is not a reference towards a Jac 638 * object). 639 * 640 * <p>Allways returns false on collections (use 641 * <code>isWrappable</code> to know if the field is wrappable). 642 * 643 * @return true if primitive 644 * @see #isReference() */ 645 646 public boolean isPrimitive() { 647 return !isReference(); 648 } 649 650 /** 651 * Tells if this field item represents a reference type (that is to 652 * say it is of a type that is a reference towards a Jac 653 * object). 654 * 655 * <p>Allways returns false on collections (use 656 * <code>isWrappable</code> to know if the field is wrappable). 657 * 658 * @return true if reference 659 * @see #isPrimitive() 660 * @see #isWrappable(Object) 661 */ 662 663 public boolean isReference() { 664 return wrappeeClass.isAssignableFrom(getType()); 665 } 666 667 /** 668 * Tells if the field item represents a wrappable type. 669 * 670 * <p>This method can be used on all kinds of field item including 671 * collections (on contrary to <code>isPrimitive</code> and 672 * <code>isReference</code>). 673 * 674 * @return true if wrappable 675 * @see #isPrimitive() 676 * @see #isReference() */ 677 678 public boolean isWrappable(Object substance) { 679 Object value = get(substance); 680 if (value==null) { 681 return wrappeeClass.isAssignableFrom(getType()); 682 } 683 return wrappeeClass.isAssignableFrom(value.getClass()); 684 } 685 686 /** 687 * Tells whether the field is transient or not. 688 */ 689 public boolean isTransient() { 690 return isCalculated ? true 691 : Modifier.isTransient(((Member)delegate).getModifiers()); 692 } 693 694 public boolean isFinal() { 695 return isCalculated ? false 696 : Modifier.isFinal(((Member)delegate).getModifiers()); 697 } 698 699 public boolean isStatic() { 700 return isCalculated ? ( isExpression ? lastField().isStatic() : getter.isStatic() ) 701 : Modifier.isStatic(((Member)delegate).getModifiers()); 702 } 703 704 public int getModifiers() { 705 if (isCalculated) 706 return Modifier.PUBLIC | Modifier.TRANSIENT; 707 else 708 return super.getModifiers(); 709 } 710 711 protected FieldItem lastField() { 712 return (FieldItem)path.get(path.size()-1); 713 } 714 715 /** 716 * Tells whether the field is transient or not. 717 */ 718 public boolean isCalculated() { 719 return isCalculated; 720 } 721 722 /** 723 * Copies this field to an other class. 724 * @param parent the class to copy the field to 725 */ 726 public FieldItem clone(ClassItem parent) { 727 FieldItem clone = null; 728 try { 729 if (isCalculated) 730 clone = new FieldItem(name,getter,parent); 731 else 732 clone = new FieldItem((Field)delegate,parent); 733 } catch(Exception e) { 734 logger.error("Failed to clone field "+this); 735 } 736 return clone; 737 } 738 739 boolean isAggregation = false; 740 public void setAggregation(boolean isAggregation) { 741 this.isAggregation = isAggregation; 742 } 743 public boolean isAggregation() { 744 return isAggregation; 745 } 746 747 public boolean startsWith(FieldItem field) { 748 return this!=field && name.startsWith(field.getName()); 749 } 750 751 public FieldItem getRelativeField(FieldItem base) { 752 if (base instanceof CollectionItem) 753 return ((CollectionItem)base).getComponentType().getField(name.substring(base.getName().length()+1)); 754 else 755 return base.getTypeItem().getField(name.substring(base.getName().length()+1)); 756 } 757 758 FieldItem oppositeRole; 759 public FieldItem getOppositeRole() { 760 if (oppositeRole!=null) 761 return oppositeRole; 762 else 763 return (FieldItem)getAttribute(RttiAC.OPPOSITE_ROLE); 764 } 765 public void setOppositeRole(FieldItem oppositeRole) { 766 this.oppositeRole = oppositeRole; 767 setAttribute(RttiAC.OPPOSITE_ROLE,oppositeRole); 768 } 769 770 public static final FieldItem[] emptyArray = new FieldItem[0]; 771 }