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.InvocationTargetException; 023 import java.lang.reflect.Method; 024 import java.util.Arrays; 025 import java.util.Vector; 026 import org.apache.log4j.Logger; 027 import org.objectweb.jac.core.Wrappee; 028 import org.objectweb.jac.util.Strings; 029 import org.objectweb.jac.util.WrappedThrowableException; 030 031 /** 032 * This class defines a meta item that corresponds to the 033 * <code>java.lang.reflect.Method</code> meta element. 034 * 035 * <p>In addition to the <code>java.lang.reflect</code> classical 036 * features, this RTTI method element is able to tell if a method is a 037 * setter, a getter, or more generally speaking, a state modifier for 038 * a given field of the object it belongs to. And, if this field is a 039 * collection (an array, a list or a map), then this meta element is 040 * able to tell if the method it represents adds or removes elements 041 * to or from this collection. 042 * 043 * <p>It also provides to introspection features for references on Jac 044 * objects. 045 * 046 * <p>For the moment, default meta informations are setted by the 047 * <code>ClassRepository</code> class using some naming 048 * conventions. In a close future, these informations will be deduced 049 * from the class bytecodes analysis at load-time. 050 * 051 * @see java.lang.reflect.Method 052 * @see #getWrittenFields() 053 * @see #getAccessedFields() 054 * @see #getAddedCollections() 055 * @see #getRemovedCollections() 056 * @see #isModifier() 057 * @see #hasAccessedReferences() 058 * 059 * @author Renaud Pawlak 060 * @author Laurent Martelli 061 */ 062 063 public class MethodItem extends AbstractMethodItem { 064 static Logger logger = Logger.getLogger("rtti.method"); 065 066 /** 067 * Transforms a method items array into a methods array containing 068 * the <code>java.lang.reflect</code> methods wrapped by the method 069 * items.<p> 070 * 071 * @param methodItems the method items 072 * @return the actual methods in <code>java.lang.reflect</code> 073 */ 074 075 public static Method[] toMethods( MethodItem[] methodItems ) { 076 Method[] res = new Method[methodItems.length]; 077 for ( int i = 0; i < methodItems.length; i++ ) { 078 if ( methodItems[i] == null ) { 079 res[i] = null; 080 } else { 081 res[i] = methodItems[i].getActualMethod(); 082 } 083 } 084 return res; 085 } 086 087 /** 088 * Default contructor to create a new method item object.<p> 089 * 090 * @param delegate the <code>java.lang.reflect.Method</code> actual 091 * meta item */ 092 093 public MethodItem(Method delegate, ClassItem parent) 094 throws InvalidDelegateException 095 { 096 super(delegate,parent); 097 Class cl = delegate.getDeclaringClass(); 098 if (Wrappee.class.isAssignableFrom(cl)) { 099 String orgName = "_org_"+delegate.getName()+ 100 (isStatic?"":"_"+Strings.getShortClassName(cl)); 101 try { 102 orgMethod = 103 cl.getDeclaredMethod(orgName,delegate.getParameterTypes()); 104 orgMethod.setAccessible(true); 105 } catch(NoSuchMethodException e) { 106 //Log.warning("No _org_ method "+orgName+" found for "+delegate); 107 } 108 } 109 } 110 111 Method orgMethod; 112 113 public final Method getOrgMethod() { 114 return orgMethod; 115 } 116 117 /** 118 * Tells if this method accesses any references of the class it 119 * belongs to. 120 * 121 * @return true if one or more references are accessed */ 122 123 public final boolean hasAccessedReferences() { 124 ((ClassItem)parent).buildFieldInfo(); 125 return numAccessedReferences>0; 126 } 127 128 FieldItem returnedField; 129 /** 130 * Returns the field item corresponding to the field value returned 131 * by this method, if any. 132 * @see #setReturnedField(FieldItem) 133 */ 134 public final FieldItem getReturnedField() { 135 return returnedField; 136 } 137 /** 138 * Sets the field returned by the method. 139 * @see #getReturnedField() 140 */ 141 public void setReturnedField(FieldItem returnedField) { 142 if (this.returnedField!=null && 143 !(returnedField.isCalculated() && this.returnedField.isCalculated())) { 144 logger.warn("overriding returned field "+this.returnedField.getName()+ 145 " for "+getParent().getName()+"."+getName()+ 146 " with "+returnedField.getName()); 147 } 148 this.returnedField = returnedField; 149 } 150 151 FieldItem setField; 152 /** 153 * Returns <em>the</em> field set by this method, if any. In other 154 * words, the field for which the method is <em>the</em> setter. A 155 * method is <em>the</em>setter of a field if it assigns this field 156 * directly with one of its argument. 157 * @see #setReturnedField(FieldItem) */ 158 public final FieldItem getSetField() { 159 return setField; 160 } 161 /** 162 * Sets <em>the</em> field set by the method. This method should 163 * not be called more than once for a gievn field. 164 * @see #getSetField() */ 165 public void setSetField(FieldItem setField) { 166 if (this.setField!=null && this.setField!=setField) { 167 logger.warn("overriding set field "+this.setField.getName()+ 168 " for "+getParent().getName()+"."+getName()+ 169 " with "+setField.getName()); 170 } 171 this.setField = setField; 172 } 173 174 /** Store the collections that are added by this method.<p> */ 175 CollectionItem[] addedCollections = null; 176 177 178 /** 179 * Get the value of the collections that are added by this 180 * method.<p> 181 * 182 * @return value of addedCollections. */ 183 184 public final CollectionItem[] getAddedCollections() { 185 ((ClassItem)parent).buildFieldInfo(); 186 return addedCollections; 187 } 188 189 /** 190 * Returns true if the method has at least one added collection 191 */ 192 public final boolean hasAddedCollections() { 193 ((ClassItem)parent).buildFieldInfo(); 194 return addedCollections!=null && addedCollections.length>0; 195 } 196 197 /** 198 * Sets the value of the collections that are added by this method.<p> 199 * 200 * @param addedCollections value to assign to addedCollections 201 * @see #addAddedCollection(CollectionItem) 202 */ 203 204 public final void setAddedCollections(CollectionItem[] addedCollections) { 205 this.addedCollections = addedCollections; 206 } 207 208 /** 209 * Adds a new added collection for this method.<p> 210 * 211 * @param addedCollection the collection to add 212 * @see #removeAddedCollection(CollectionItem) 213 */ 214 215 public final void addAddedCollection(CollectionItem addedCollection) { 216 if (addedCollections == null) { 217 addedCollections = new CollectionItem[] { addedCollection }; 218 } else { 219 CollectionItem[] tmp = new CollectionItem[addedCollections.length + 1]; 220 System.arraycopy(addedCollections, 0, tmp, 0, addedCollections.length); 221 tmp[addedCollections.length] = addedCollection; 222 addedCollections = tmp; 223 } 224 } 225 226 /** 227 * Removes an existing added collection for this method.<p> 228 * 229 * @param collection the collection to add 230 * @see #addAddedCollection(CollectionItem) 231 */ 232 public final void removeAddedCollection(CollectionItem collection) { 233 if (addedCollections != null) { 234 Vector v = new Vector(Arrays.asList(addedCollections)); 235 v.remove(collection); 236 addedCollections = new CollectionItem[v.size()]; 237 System.arraycopy(v.toArray(),0,addedCollections,0,v.size()); 238 } 239 } 240 241 /** 242 * Gets the method represented by this method item.<p> 243 * 244 * @return the actual method 245 */ 246 247 public final Method getActualMethod() { 248 return (Method)delegate; 249 } 250 251 public final String getName() { 252 return ((Method)delegate).getName(); 253 } 254 255 public final Class getType() { 256 return ((Method)delegate).getReturnType(); 257 } 258 259 public Class[] getParameterTypes() { 260 return ((Method)delegate).getParameterTypes(); 261 } 262 263 int collectionIndexArgument = -1; 264 /** 265 * Returns the number of the argument which is the index of a 266 * modified collection if any, -1 otherwise. 267 */ 268 public int getCollectionIndexArgument() { 269 return collectionIndexArgument; 270 } 271 /** 272 * Sets collectionIndexArgument 273 * @see #getCollectionIndexArgument() 274 */ 275 public void setCollectionIndexArgument(int collectionIndexArgument) { 276 this.collectionIndexArgument = collectionIndexArgument; 277 } 278 279 int collectionItemArgument = -1; 280 /** 281 * Returns the number of the argument which is the item that will be added 282 * to a collection if any, -1 otherwise. 283 */ 284 public int getCollectionItemArgument() { 285 return collectionItemArgument; 286 } 287 /** 288 * Sets collectionItemArgument 289 * @see #getCollectionItemArgument() 290 */ 291 public void setCollectionItemArgument(int collectionItemArgument) { 292 this.collectionItemArgument = collectionItemArgument; 293 } 294 295 public void addAccessedField(FieldItem accessedField) { 296 super.addAccessedField(accessedField); 297 if (getType()!=void.class) 298 accessedField.addDependentMethod(this); 299 } 300 301 /** 302 * Invokes this method on the given object and with the given 303 * parameters values. 304 * 305 * @param object a class this method belongs to intance 306 * @param parameters the values of the parameters to invoke this 307 * method with 308 */ 309 public Object invoke(Object object, Object[] parameters) 310 { 311 logger.debug(toString()+" invoke("+object+","+Arrays.asList(parameters)+")"); 312 if (!isStatic() && object==null) 313 throw new NullPointerException( 314 "Invoking instance method "+ 315 parent.getName()+"."+this+" on null"); 316 try { 317 return ((Method)delegate).invoke(object,parameters); 318 } catch (IllegalArgumentException e) { 319 Class cl = (Class)getParent().getDelegate(); 320 if (!cl.isAssignableFrom(object.getClass())) { 321 throw new IllegalArgumentException( 322 getLongName()+": called object "+Strings.hex(object)+ 323 " is not an instance of "+cl.getName()); 324 } 325 if (parameters.length == getParameterCount()) { 326 checkParameters(parameters); 327 } 328 throw e; 329 } catch (Exception e) { 330 logger.info("caught exception "+e); 331 throw new WrappedThrowableException(e); 332 } 333 } 334 335 /** 336 * Checks the type of parameters 337 * 338 * @param parameters parameters to check 339 */ 340 void checkParameters(Object[] parameters) { 341 Class[] types = getParameterTypes(); 342 for (int i=0; i<types.length; i++) { 343 if (!types[i].isAssignableFrom(parameters[i].getClass())) 344 throw new IllegalArgumentException( 345 getLongName()+", argument n°"+(i+1)+":"+ 346 parameters[i].getClass().getName()+ 347 " cannot be converted to "+types[i].getName()); 348 } 349 } 350 351 /** 352 * Invokes this static method with the given parameters values. 353 * 354 * @param parameters the values of the parameters to invoke this 355 * method with */ 356 357 public final Object invokeStatic(Object[] parameters) 358 { 359 if (!isStatic()) 360 throw new RuntimeException("Cannot invokeStatic a non static method: "+getLongName()); 361 // logger.debug(toString()+" invoke("+object+","+Arrays.asList(parameters)+")"); 362 try { 363 return ((Method)delegate).invoke(null,parameters); 364 } catch (IllegalArgumentException e) { 365 if (parameters.length == getParameterCount()) { 366 checkParameters(parameters); 367 } 368 throw e; 369 } catch (Exception e) { 370 logger.info("Caught exception in "+getFullName(),e); 371 throw new WrappedThrowableException(e); 372 } 373 } 374 375 /** 376 * Invokes a method with uninitialized parameters. 377 * 378 * <p>The parameters array is initialized before the invocation 379 * with default values. 380 * 381 * @param object the target object 382 * @param parameters the maybe initialized values of the parameters 383 * to invoke this method with (primitive parameters can be null) */ 384 385 public final Object invokeWithInit(Object object, 386 Object[] parameters) 387 throws IllegalAccessException, InvocationTargetException 388 { 389 390 Class[] paramTypes = getParameterTypes(); 391 for (int i=0; i< parameters.length; i++) { 392 if (parameters[i]==null) { 393 if (paramTypes[i] == float.class) { 394 parameters[i] = new Float(0.0); 395 } else if (paramTypes[i] == long.class) { 396 parameters[i] = new Long(0); 397 } else if (paramTypes[i] == double.class) { 398 parameters[i] = new Double(0.0); 399 } else if (paramTypes[i] == byte.class) { 400 parameters[i] = new Byte((byte)0); 401 } else if (paramTypes[i] == char.class) { 402 parameters[i] = new Character(' '); 403 } else if (paramTypes[i] == short.class) { 404 parameters[i] = new Short((short)0); 405 } else if (paramTypes[i] == int.class) { 406 parameters[i] = new Integer(0); 407 } else if (paramTypes[i] == boolean.class) { 408 parameters[i] = Boolean.FALSE; 409 } 410 } 411 } 412 return ((Method)delegate).invoke(object,parameters); 413 } 414 415 /** Tells wether the method returns the value of a field */ 416 public final boolean isGetter() { 417 ((ClassItem)parent).buildFieldInfo(); 418 return returnedField!=null; 419 } 420 421 public final boolean isCollectionGetter() { 422 ((ClassItem)parent).buildFieldInfo(); 423 return returnedField!=null && (returnedField instanceof CollectionItem); 424 } 425 426 /** Tells wether the method is <em>the</em> setter of a field */ 427 public final boolean isSetter() { 428 ((ClassItem)parent).buildFieldInfo(); 429 return setField!=null; 430 } 431 432 /** Tells wether the method is an adder of a collection */ 433 public final boolean isAdder() { 434 ((ClassItem)parent).buildFieldInfo(); 435 return addedCollections!=null && addedCollections.length>0; 436 } 437 438 439 /** Stores the collections that are removed by this method.<p> */ 440 CollectionItem[] removedCollections = null; 441 442 /** 443 * Gets the value of the collections that are removed by this 444 * method.<p> 445 * 446 * @return value of removedCollections. */ 447 448 public final CollectionItem[] getRemovedCollections() { 449 ((ClassItem)parent).buildFieldInfo(); 450 return removedCollections; 451 } 452 453 public final CollectionItem getRemovedCollection() { 454 CollectionItem[] colls = getRemovedCollections(); 455 return ((colls != null) ? colls[0] : null); 456 } 457 458 /** 459 * Returns true if the method has at least one removed collection 460 */ 461 462 public final boolean hasRemovedCollections() { 463 ((ClassItem)parent).buildFieldInfo(); 464 return removedCollections!=null && removedCollections.length>0; 465 } 466 467 /** 468 * Sets the value of the collections that are removed by this method.<p> 469 * 470 * @param removedCollections value to assign to removedCollections 471 * @see #addRemovedCollection(CollectionItem) 472 */ 473 474 public final void setRemovedCollections(CollectionItem[] removedCollections) { 475 this.removedCollections = removedCollections; 476 } 477 478 /** 479 * Adds a new removed collection for this method.<p> 480 * 481 * @param removedCollection the collection to add 482 * @see #setRemovedCollections(CollectionItem[]) 483 * @see #removeRemovedCollection(CollectionItem) 484 */ 485 486 public final void addRemovedCollection(CollectionItem removedCollection) { 487 if (removedCollections == null) { 488 removedCollections = new CollectionItem[] { removedCollection }; 489 } else { 490 CollectionItem[] tmp = new CollectionItem[removedCollections.length+1]; 491 System.arraycopy(removedCollections, 0, tmp, 0, 492 removedCollections.length); 493 tmp[removedCollections.length] = removedCollection; 494 removedCollections = tmp; 495 } 496 } 497 498 /** 499 * Removes an existing removed collection for this method.<p> 500 * 501 * @param collection the collection to remove 502 * @see #addRemovedCollection(CollectionItem) 503 */ 504 public final void removeRemovedCollection(CollectionItem collection) { 505 if (removedCollections != null) { 506 Vector v = new Vector(Arrays.asList(removedCollections)); 507 v.remove(collection); 508 removedCollections = new CollectionItem[v.size()]; 509 System.arraycopy(v.toArray(),0,removedCollections,0,v.size()); 510 } 511 } 512 513 /** Tells wether the method is a remover of a collection */ 514 public final boolean isRemover() { 515 ((ClassItem)parent).buildFieldInfo(); 516 return removedCollections!=null && removedCollections.length>0; 517 } 518 519 public final boolean isAccessor() { 520 ((ClassItem)parent).buildFieldInfo(); 521 return accessedFields!=null && accessedFields.length>0; 522 } 523 524 public final boolean isWriter() { 525 ((ClassItem)parent).buildFieldInfo(); 526 return hasWrittenFields(); 527 } 528 529 public final boolean isCollectionAccessor() { 530 ((ClassItem)parent).buildFieldInfo(); 531 if (accessedFields!=null) { 532 for (int i=0; i<accessedFields.length; i++) { 533 if (accessedFields[i] instanceof CollectionItem) { 534 return true; 535 } 536 } 537 } 538 return false; 539 } 540 541 public final boolean isReferenceAccessor() { 542 ((ClassItem)parent).buildFieldInfo(); 543 if (accessedFields!=null) { 544 for (int i=0; i<accessedFields.length; i++) { 545 if (accessedFields[i].isReference()) { 546 return true; 547 } 548 } 549 } 550 return false; 551 } 552 553 public final boolean isCollectionSetter() { 554 ((ClassItem)parent).buildFieldInfo(); 555 return setField instanceof CollectionItem; 556 } 557 558 public final boolean isFieldSetter() { 559 ((ClassItem)parent).buildFieldInfo(); 560 return setField!=null && setField.isPrimitive(); 561 } 562 563 public final boolean isReferenceSetter() { 564 ((ClassItem)parent).buildFieldInfo(); 565 return setField!=null && setField.isReference(); 566 } 567 568 public final boolean isFieldGetter() { 569 ((ClassItem)parent).buildFieldInfo(); 570 return !(returnedField instanceof CollectionItem) 571 && returnedField.isPrimitive(); 572 } 573 574 public final boolean isReferenceGetter() { 575 ((ClassItem)parent).buildFieldInfo(); 576 return returnedField!=null && returnedField.isReference(); 577 } 578 579 /** Tells wether the method was introduced by JAC */ 580 public final boolean isJacMethod() { 581 return ClassRepository.isJacMethod(getName()); 582 } 583 584 public final static MethodItem[] emptyArray = new MethodItem[0]; 585 }// MethodItem