001 /* 002 Copyright (C) 2001-2003 Renaud Pawlak <renaud@aopsys.com> 003 004 This program is free software; you can redistribute it and/or modify 005 it under the terms of the GNU Lesser General Public License as 006 published by the Free Software Foundation; either version 2 of the 007 License, or (at your option) any later version. 008 009 This program is distributed in the hope that it will be useful, but 010 WITHOUT ANY WARRANTY; without even the implied warranty of 011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 Lesser General Public License for more details. 013 014 You should have received a copy of the GNU Lesser General Public 015 License along with this program; if not, write to the Free Software 016 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 017 USA */ 018 019 package org.objectweb.jac.core; 020 021 import gnu.regexp.RE; 022 import gnu.regexp.REException; 023 import gnu.regexp.RESyntax; 024 import java.util.Arrays; 025 import java.util.Collection; 026 import java.util.HashSet; 027 import java.util.Hashtable; 028 import java.util.Iterator; 029 import java.util.List; 030 import java.util.StringTokenizer; 031 import java.util.Vector; 032 import org.apache.log4j.Logger; 033 import org.objectweb.jac.core.rtti.AbstractMethodItem; 034 import org.objectweb.jac.core.rtti.ClassItem; 035 import org.objectweb.jac.core.rtti.ClassRepository; 036 import org.objectweb.jac.core.rtti.CollectionItem; 037 import org.objectweb.jac.core.rtti.ConstructorItem; 038 import org.objectweb.jac.core.rtti.FieldItem; 039 import org.objectweb.jac.core.rtti.MethodItem; 040 import org.objectweb.jac.util.ExtArrays; 041 import org.objectweb.jac.util.Strings; 042 043 /** 044 * This class can be used by JAC aspect components to easily define a 045 * set of method points on the base program that the aspects will use to 046 * modify its behavior. 047 * 048 * <p>A method pointcut is defined through four pointcut 049 * expressions. For the moment, these pointcut expressions are a 050 * simple extension of regular expressions -- in an EMACS_LISP syntax 051 * (see the GNU-regexp tutorial) that can be combined with the 052 * <code>&&</code> operator (in this case all the regexps must match) 053 * or the <code>||</code> operator (in this case, only one regexp must 054 * match). Before a regexp, you can use the <code>!</code> (not) 055 * operator to inverse the matching. 056 * 057 * <p>Depending on the pointcut expression, you can also 058 * use keywords that will be dynamically interpreted. We intend to 059 * provide a more complete and suited desciption language. Note that 060 * for the moment, the tricky cases that cannot be handled by these 061 * expressions can be treated by programming the 062 * <code>AspectComponent.whenUsingNewInstance</code> method. 063 * 064 * <ul><li><i>The host expression</i>: filters the components that 065 * belong or not to the pointcut on a location basis. To be activated, 066 * the name of the host where the pointcut is currently applied must 067 * match this expression.</li> 068 * 069 * <li><i>The wrappee expression</i>: filters the components that 070 * belong or not to the pointcut on a per-component basis. This 071 * expression assumes that the component is named (that the naming 072 * aspect is woven). If you need to modify all the components of the 073 * same class equaly, then this expression should be <code>".*"</code> 074 * and the <i>wrappee-class expression</i> should be used.</li> 075 * 076 * <li><i>The wrappee-class expression</i>: filters the components 077 * that belong or not to the pointcut on a per-class basis. For 078 * instance, <code>"A || B"</code> says that only the classes that are 079 * named A or B belong to the pointcut; <code>packagename.* && 080 * classname</code> matches the class that belong to 081 * <code>packagename</code> and that are named 082 * <code>classname</code>.</li> 083 * 084 * <li><i>The wrappee-method expression</i>: for all the components 085 * that match the two previous expressions, defines which methods must 086 * be modified by the pointcut. For instance, 087 * <code>"set.*(int):void"</code> matches all the methods that have 088 * name that begins with "set", that take only one integer argument, 089 * and that return nothing. The regular expression can contain several 090 * builtin keywords to automatically match methods with special 091 * semantics (for instance, <code>ALL</code>: all the methods of the 092 * class, <code>MODIFIERS</code>: all the state modifiers, 093 * <code>ACCESSORS</code>: all the state setters, 094 * <code>GETTERS(fielname1,fieldname2)</code>: the getter for the 095 * given field, <code>SETTERS(...)</code>: the setter for the given 096 * field), <code>WRITERS(...)</code>: all the methods which modify the 097 * given field. For instance, <code>FIELDSETTERS && !.*(int).*</code> 098 * matches all the field setters, excluding the ones for the integer 099 * fields.</li></ul> 100 * 101 * <p>For more informations on pointcut expressions, please see the <a 102 * href="doc/tutorial.html#3.2.5>programmer's guide</a>. 103 * 104 * <p>A pointcut is also related to a wrapping class and a wrapping 105 * method (and a precise wrapper, instance of the wrapping class, can 106 * be specified if it is known). This couple implements the aspect 107 * code in all the points that matches the over-depicted pointcut 108 * expression. 109 * 110 * <p>Finally, when a pointcut is applied to the matching elements, 111 * wrappers (instances of the wrapping class) are created (except if 112 * the intialization wrapper is not null). In this case, the 113 * <code>one2one</code> flag tells if one new wrapper instance nust be 114 * created for each base component or if one unique wrapper instance 115 * must be used for all the components. 116 * 117 * @author <a href="http://cedric.cnam.fr/~pawlak/index-english.html">Renaud Pawlak</a> 118 * @see AspectComponent */ 119 120 public class MethodPointcut extends Pointcut { 121 static Logger logger = Logger.getLogger("pointcut"); 122 static Logger loggerName = Logger.getLogger("pointcut.name"); 123 static Logger loggerHost = Logger.getLogger("pointcut.host"); 124 static Logger loggerPath = Logger.getLogger("pointcut.path"); 125 static Logger loggerKeywords = Logger.getLogger("pointcut.keywords"); 126 static Logger loggerCreate = Logger.getLogger("pointcut.create"); 127 static Logger loggerWrappers = Logger.getLogger("wappers"); 128 129 Vector wrappeeExprs = new Vector(); 130 Vector wrappeeRegexps = new Vector(); 131 Vector wrappeeClassExprs = new Vector(); 132 Vector wrappeeClassRegexps = new Vector(); 133 Vector wrappeeMethodExprs = new Vector(); 134 Vector wrappeeMethodRegexps = new Vector(); 135 Vector hostExprs = new Vector(); 136 Vector hostRegexps = new Vector(); 137 Vector iwrappeeExprs = new Vector(); 138 Vector iwrappeeClassExprs = new Vector(); 139 Vector iwrappeeMethodExprs = new Vector(); 140 Vector ihostExprs = new Vector(); 141 String wrappeeExpr; 142 String wrappeeClassExpr; 143 String wrappeeMethodExpr; 144 String hostExpr; 145 String wrappingClassName; 146 String methodName; 147 Object[] methodArgs; 148 String exceptionHandler; 149 boolean one2one = true; 150 boolean allInstances = false; 151 boolean allHosts = false; 152 boolean allClasses = false; 153 AspectComponent aspectComponent = null; 154 155 /** Class of the wrapper */ 156 ClassItem wrapperClass; 157 /** The wrapping method */ 158 //MethodItem wrappingMethod; 159 160 static String[] wrappeeKeywords = new String[] { 161 "ALL" 162 }; 163 static String[] classKeywords = new String[] { 164 "ALL", 165 "COLLECTIONS" 166 }; 167 static String[] methodKeywords = new String[] { 168 "ALL", 169 "STATICS", 170 "CONSTRUCTORS", 171 "MODIFIERS", 172 "REFACCESSORS", 173 "COLACCESSORS", 174 "ACCESSORS", 175 "COLSETTERS", 176 "FIELDSETTERS", 177 "REFSETTERS", 178 "SETTERS", /* args=a list of fields or tags */ 179 "WRITERS", /* args=a list of fields or tags */ 180 "COLGETTERS", 181 "FIELDGETTERS", 182 "REFGETTERS", 183 "GETTERS", /* args=a list of fields or tags */ 184 "ADDERS", /* no args | args=list of collection or tags */ 185 "REMOVERS" /* no args | args=list of collection or tags */ 186 }; 187 static String[] hostKeywords = new String[] { 188 "ALL" 189 }; 190 191 /** 192 * Returns a readable description of the pointcut. */ 193 194 public String toString() { 195 return "pointcut {"+wrappingClassName+","+ 196 methodName+ 197 "}->{"+wrappeeExpr+","+wrappeeClassExpr+","+ 198 wrappeeMethodExpr+","+hostExpr+"}"; 199 } 200 201 Wrapper commonWrapper = null; 202 Wrapper initWrapper = null; 203 204 /** 205 * Instantiates a new pointcut with the given characterisitics. 206 * 207 * @param aspectComponent the aspect component this pointcut 208 * belongs to 209 * @param wrappeeExpr the wrappee definition that matches the names 210 * as defined by the naming aspect 211 * @param wrappeeClassExpr the wrappee class expression (matches 212 * the fully qualified class name) 213 * @param wrappeeMethodExpr the wrappee method expression (matches 214 * the full method name as defined by the rtti) 215 * @param initWrapper the instance of the wrapper used by this 216 * pointcut, if null a new one is automatically created depending 217 * on the one2one flag 218 * @param wrappingClassName the name of the wrapper class 219 * @param methodName the name of the aspect component 220 * method to upcall when a mathing object is used 221 * @param methodArgs the argument values for this method 222 * method to upcall when a matching object is used 223 * @param hostExpr a regular expression that macthes the hosts 224 * where the pointcut has to be applied (if null or empty string, 225 * default is ".*" which means that the pointcut will be applied on 226 * all the hosts) 227 * @param one2one true if each new wrapped instance corresponds to 228 * one different wrapper (it has no effect if 229 * <code>initWrapper</code> is not null 230 */ 231 public MethodPointcut(AspectComponent aspectComponent, 232 String wrappeeExpr, 233 String wrappeeClassExpr, 234 String wrappeeMethodExpr, 235 Wrapper initWrapper, 236 String wrappingClassName, 237 String methodName, 238 Object[] methodArgs, 239 String hostExpr, 240 String exceptionHandler, 241 boolean one2one) { 242 243 this.aspectComponent = aspectComponent; 244 this.wrappeeExpr = wrappeeExpr; 245 this.wrappeeClassExpr = wrappeeClassExpr; 246 this.wrappeeMethodExpr = wrappeeMethodExpr; 247 this.hostExpr = hostExpr; 248 this.initWrapper = initWrapper; 249 this.wrappingClassName = wrappingClassName; 250 this.methodName = methodName; 251 this.methodArgs = methodArgs; 252 this.exceptionHandler = exceptionHandler; 253 this.one2one = one2one; 254 255 parseExpr("wrappee class expression", null, null, 256 wrappeeClassExpr, classKeywords, 257 wrappeeClassExprs, iwrappeeClassExprs); 258 wrappeeClassRegexps = buildRegexps(wrappeeClassExprs); 259 260 parseExpr("host expression", null, null, 261 hostExpr, hostKeywords, 262 hostExprs, ihostExprs); 263 hostRegexps = buildRegexps(hostExprs); 264 265 if (wrappeeExpr.equals("ALL") || wrappeeExpr.equals(".*")) { 266 allInstances = true; 267 } 268 if (hostExpr.equals("ALL") || hostExpr.equals(".*")) { 269 allHosts = true; 270 } 271 if (wrappeeClassExpr.equals("ALL") || wrappeeClassExpr.equals(".*")) { 272 allClasses = true; 273 } 274 275 if (!allInstances) { 276 parseExpr("wrappee expression", null, null, 277 wrappeeExpr, wrappeeKeywords, 278 wrappeeExprs, iwrappeeExprs); 279 280 wrappeeRegexps = buildRegexps(wrappeeExprs); 281 } 282 283 loggerCreate.debug(aspectComponent+" new pointcut "+this); 284 } 285 286 /** 287 * Build a vector of regular expressions 288 * @param patterns a collection of strings 289 * @return a vector of the size of patterns filled with RE. 290 */ 291 Vector buildRegexps(Vector patterns) { 292 Vector result = new Vector(patterns.size()); 293 Iterator i = patterns.iterator(); 294 while (i.hasNext()) { 295 String pattern = (String)i.next(); 296 try { 297 result.add(buildRegexp(pattern)); 298 } catch(REException e) { 299 logger.error("invalid regexp \""+pattern+"\":"+e); 300 } 301 } 302 return result; 303 } 304 305 public static RE buildRegexp(String pattern) throws REException { 306 return new RE(Strings.replace(pattern,"$","\\$"),0, 307 RESyntax.RE_SYNTAX_EMACS); 308 } 309 310 /** 311 * Instanciate the wrapper, and initialize wrapperClass 312 * @parapm wrappee unused 313 */ 314 Wrapper buildWrapper(Wrappee wrappee) { 315 try { 316 if (wrapperClass==null) { 317 wrapperClass = ClassRepository.get().getClass(wrappingClassName); 318 } 319 if (methodArgs!=null) { 320 if (wrapperClass.isInner()) 321 return (Wrapper)wrapperClass.newInstance( 322 ExtArrays.add(0,aspectComponent,methodArgs)); 323 else 324 return (Wrapper)wrapperClass.newInstance( 325 ExtArrays.add(0,aspectComponent,methodArgs)); 326 } else { 327 if (wrapperClass.isInner()) 328 return (Wrapper)wrapperClass.newInstance( 329 new Object[] {aspectComponent,aspectComponent}); 330 else 331 return (Wrapper)wrapperClass.newInstance( 332 new Object[] {aspectComponent}); 333 } 334 } catch(Exception e) { 335 logger.error("buildWrapper failed for "+wrappee,e); 336 } 337 return null; 338 } 339 340 /** 341 * Applies this pointcut to the given wrappee. 342 * 343 * <p>The pointcut application consists in wrapping the wrappee 344 * accordingly to the pointcut description. Note that in JAC, the 345 * pointcut is usually applied on a per-component basis, and when 346 * the component (wrappee) is used for the first time. 347 * 348 * @param wrappee the component the pointcut is applied to 349 * @param cl class the pointcut is applied to 350 */ 351 352 public synchronized void applyTo(Wrappee wrappee, ClassItem cl) { 353 354 // REGRESSION: CANNOT HANDLE CLONES FOR THE MOMENT 355 //if( wrappee!=null && wrappee.isCloned() ) { 356 // Log.trace("pointcut","do not apply aspects on clones"); 357 // return; 358 //} 359 360 logger.info("apply "+this+" on "+wrappee+" - "+cl); 361 362 363 if (!isClassMatching(wrappee,cl)) { return; } 364 Logger classLogger = Logger.getLogger("pointcut."+cl); 365 if (classLogger.isDebugEnabled()) 366 classLogger.debug("class is matching"); 367 368 if (!isHostMatching(wrappee,cl)) { return; } 369 if (classLogger.isDebugEnabled()) 370 classLogger.debug("host is matching"); 371 372 if (!isNameMatching(wrappee,cl)) return; 373 if (classLogger.isDebugEnabled()) 374 classLogger.debug("name is matching"); 375 376 // upcalls the method if exist 377 if (methodName!=null) { 378 try { 379 classLogger.debug( 380 "Upcalling "+aspectComponent.getClass().getName()+ 381 "."+methodName+"("+Arrays.asList(methodArgs)+")"); 382 Object[] args = ExtArrays.add(0,wrappee,methodArgs); 383 ClassRepository.get().getClass(aspectComponent.getClass()) 384 .invoke(aspectComponent, methodName, args); 385 } catch(Exception e) { 386 logger.error("Upcalling failed",e); 387 } 388 } 389 390 // stops if no wrapping infos if given 391 if (initWrapper==null 392 && wrappingClassName==null) 393 return; 394 Collection methodsToWrap = 395 wrappee!=null ? getMatchingMethodsFor(wrappee,cl) : getMatchingStaticMethodsFor(cl); 396 Wrapper wrapper = null; 397 if (initWrapper!=null) { 398 wrapper = commonWrapper = initWrapper; 399 wrapperClass = ClassRepository.get().getClass(wrapper); 400 } else { 401 if (one2one) { 402 wrapper = buildWrapper(wrappee); 403 } else { 404 if (commonWrapper==null) 405 commonWrapper = buildWrapper(wrappee); 406 wrapper = commonWrapper; 407 } 408 } 409 410 //if (wrappingMethod==null) { 411 // wrappingMethod = wrapperClass.getMethod(wrappingMethodName); 412 //} 413 414 if (methodsToWrap!=null && methodsToWrap.size()>0) { 415 classLogger.debug( 416 "applying "+wrappingClassName+ 417 " on "+cl.getName()+" ("+ 418 NameRepository.get().getName(wrappee)+")"); 419 classLogger.debug("methods to wrap="+methodsToWrap); 420 } 421 422 loggerWrappers.debug("new pointcut: wrapper="+wrapper+" methods to wrap="+methodsToWrap); 423 424 // wrap the methods 425 //Log.trace("pointcut.wrap","exception handlers for "+wrapper.getClass()+": "+eh); 426 Iterator it = methodsToWrap.iterator(); 427 boolean wrapped = false; 428 while (it.hasNext()) { 429 AbstractMethodItem method = (AbstractMethodItem)it.next(); 430 classLogger.debug( 431 "Wrapping "+method.getLongName()+" with "+wrappingClassName+ 432 " on "+wrappee+" - "+cl.getName()); 433 //wrapped = wrapped || Wrapping.wrapMethod(wrappee,wrapper,method); 434 if (Wrapping.wrapMethod(wrappee,wrapper,method) && !wrapped) { 435 // postponing this at after the loop seems to have 436 // strange effects on static methods wrapping 437 Wrapping.wrap(wrappee,cl,wrapper); 438 wrapped = true; 439 } 440 441 // install exeption handler if needed 442 if (exceptionHandler!=null) { 443 Wrapping.addExceptionHandler(wrappee,wrapper, 444 exceptionHandler,method); 445 } 446 } 447 loggerWrappers.debug("wrapped = "+wrapped); 448 if (methodsToWrap.size()==0 && one2one) { 449 Wrapping.wrap(wrappee,cl,wrapper); 450 } 451 } 452 453 /* Cache of matching methods (ClassItem -> Vector of AbstractMethodItem)*/ 454 Hashtable cache = new Hashtable(); 455 456 /* Cache of matching static methods (ClassItem -> Vector of AbstractMethodItem)*/ 457 Hashtable staticsCache = new Hashtable(); 458 459 /** 460 * Gets the methods of the wrappee that are modified by this 461 * pointcut. 462 * 463 * @param wrappee the component to test 464 * @param cli the class of the wrappee 465 * @return a vector containing the matching method items 466 */ 467 protected Collection getMatchingMethodsFor(Wrappee wrappee, ClassItem cli) { 468 //Log.trace("pointcut.match."+cli,"getting matching methods for "+ 469 // cli.getName()+"("+wrappee+") "+wrappeeMethodExpr); 470 String name = null; 471 if (wrappee!=null) { 472 name = cli.getName(); 473 } 474 Collection result = (Collection)cache.get(name); 475 if (result==null) { 476 result = parseMethodExpr(wrappee,cli,wrappeeMethodExpr); 477 cache.put(name,result); 478 //Log.trace("pointcut.match."+cli,wrappeeMethodExpr+" -> "+result); 479 } else { 480 //Log.trace("pointcut.match."+cli,2,"methods cache hit for "+ 481 // cli.getName()+"("+wrappee+")"); 482 } 483 return result; 484 } 485 486 protected Collection getMatchingStaticMethodsFor(ClassItem cli) { 487 //Log.trace("pointcut.match."+cli, 488 // "getting static matching methods for "+cli.getName()); 489 String name = cli.getName(); 490 Collection result = (Collection)staticsCache.get(name); 491 if (result==null) { 492 //Log.trace("pointcut.match."+cli,"method expr="+wrappeeMethodExpr); 493 result = parseMethodExpr(null,cli,wrappeeMethodExpr); 494 staticsCache.put(name,result); 495 } else { 496 //Log.trace("pointcut.match."+cli,2, 497 // "methods cache hit for "+cli.getName()); 498 } 499 return result; 500 } 501 502 /** 503 * @param wrappee the object the pointcut applies to 504 * @param cli the class the pointcut applies to (in case 505 * wrappee==null, for static methods) 506 * @param expr the pointcut expression 507 * @return A set of method matching the pointcut for the wrappee or class 508 */ 509 public Collection parseMethodExpr(Wrappee wrappee, ClassItem cli, String expr) { 510 //Log.trace("pointcut.parse","parseMethodExpr "+expr+" for "+cli); 511 String[] exprs = Strings.split(expr,"&&"); 512 Collection result = new HashSet(); 513 514 if (wrappee==null) { 515 result.addAll(cli.getAllStaticMethods()); 516 } else { 517 result.addAll(cli.getAllInstanceMethods()); 518 result.addAll(cli.getConstructors()); 519 } 520 for (int i=0; i<exprs.length; i++) { 521 String curExpr; 522 boolean inv = false; 523 exprs[i] = exprs[i].trim(); 524 if (exprs[i].charAt(0)=='!') { 525 inv = true; 526 curExpr = exprs[i].substring(1).trim(); 527 } else { 528 curExpr = exprs[i]; 529 } 530 531 String[] subExprs = Strings.split(curExpr,"||"); 532 HashSet subExprResult = new HashSet(); 533 for(int j=0; j<subExprs.length; j++) { 534 String curSubExpr = subExprs[j].trim(); 535 filterMethodKeywords(wrappee,cli,curSubExpr,inv,result,subExprResult); 536 } 537 //System.out.println((inv?"!":"")+curExpr+" -> "+subExprResult); 538 result = subExprResult; 539 } 540 return result; 541 } 542 543 /** 544 * Adds methods from source that match an expression to a collection 545 * 546 * @param wrappee object to match with 547 * @param cli class to macth with, used if wrappee==null 548 * @param expr the expression to match 549 * @param inv wether to keep or reject matching methods 550 * @param source method items to chose from 551 * @param dest collection to the matching methods to 552 */ 553 protected void filterMethodKeywords(Object wrappee, ClassItem cli, 554 String expr, boolean inv, 555 Collection source, Collection dest) { 556 557 //System.out.println("EXPR="+(inv?"!":"")+expr+", CLI="+cli); 558 String keyword = null; 559 for (int i=0; i<methodKeywords.length && keyword==null; i++) { 560 if (expr.startsWith(methodKeywords[i])) { 561 keyword = methodKeywords[i]; 562 //System.out.println(" KEYWORD="+keyword); 563 List parameters = null; 564 565 Iterator it = source.iterator(); 566 boolean add = false; 567 while (it.hasNext()) { 568 AbstractMethodItem method = (AbstractMethodItem)it.next(); 569 //System.out.println(" TESTING="+method); 570 add = false; 571 if (keyword.equals("ALL")) { 572 add = !inv; 573 } else if (keyword.equals("MODIFIERS")) { 574 if (parameters==null) 575 parameters = parseParameters(expr.substring(keyword.length()),cli); 576 add = (isWriter(method,parameters) || 577 isAdder(method,parameters) || 578 isRemover(method,parameters) || 579 isCollectionModifier(method,parameters)) 580 ^ inv; 581 } else if (keyword.equals("ACCESSORS")) { 582 add = method.isAccessor() ^ inv; 583 } else if (keyword.equals("REMOVERS")) { 584 if (parameters==null) 585 parameters = parseParameters(expr.substring(keyword.length()),cli); 586 add = isRemover(method,parameters) ^ inv; 587 } else if (keyword.equals("ADDERS")) { 588 if (parameters==null) 589 parameters = parseParameters(expr.substring(keyword.length()),cli); 590 add = isAdder(method,parameters) ^ inv; 591 } else if (keyword.equals("SETTERS")) { 592 if (parameters==null) 593 parameters = parseParameters(expr.substring(keyword.length()),cli); 594 add = isSetter(method,parameters) ^ inv; 595 } else if (keyword.equals("STATICS")) { 596 add = method.isStatic() ^ inv; 597 } else if (keyword.equals("CONSTRUCTORS")) { 598 add = (method instanceof ConstructorItem) ^ inv; 599 } else if (keyword.equals("COLGETTERS")) { 600 add = method.isCollectionGetter() ^ inv; 601 } else if (keyword.equals("COLACCESSORS")) { 602 if (parameters==null) 603 parameters = parseParameters(expr.substring(keyword.length()),cli); 604 add = isCollectionAccessor(method,parameters) ^ inv; 605 } else if (keyword.equals("FIELDGETTERS")) { 606 add = method.isFieldGetter() ^ inv; 607 } else if (keyword.equals("REFGETTERS")) { 608 add = method.isReferenceGetter() ^ inv; 609 } else if (keyword.equals("REFACCESSORS")) { 610 if (parameters==null) 611 parameters = parseParameters(expr.substring(keyword.length()),cli); 612 add = isReferenceAccessor(method,parameters) ^ inv; 613 } else if (keyword.equals("COLSETTERS")) { 614 add = method.isCollectionSetter() ^ inv; 615 } else if (keyword.equals("FIELDSETTERS")) { 616 add = method.isFieldSetter() ^ inv; 617 } else if (keyword.equals("REFSETTERS")) { 618 add = method.isReferenceSetter() ^ inv; 619 } else if (keyword.equals("WRITERS")) { 620 if (parameters==null) 621 parameters = parseParameters(expr.substring(keyword.length()),cli); 622 add = isWriter(method,parameters) ^ inv; 623 } else if (keyword.equals("GETTERS")) { 624 if (parameters==null) 625 parameters = parseParameters(expr.substring(keyword.length()),cli); 626 add = isGetter(method,parameters) ^ inv; 627 } 628 if (add) { 629 dest.add(method.getConcreteMethod()); 630 it.remove(); 631 } 632 } 633 } 634 } 635 636 // if no keyword was found, use regular expression matching 637 if (keyword==null) { 638 try { 639 /* 640 System.out.println("regexp matching for "+ 641 (inv?"!":"")+expr+" -> "+ 642 wrappingMethodName+" on "+wrappee); 643 System.out.println("Methods = "+source); 644 */ 645 RE re = new RE(Strings.replace(expr,"$","\\$"),0, 646 RESyntax.RE_SYNTAX_EMACS); 647 Iterator it = source.iterator(); 648 while (it.hasNext()) { 649 AbstractMethodItem method = (AbstractMethodItem)it.next(); 650 if (re.isMatch(method.toString()) ^ inv) { 651 dest.add(method); 652 //System.out.println(" -> ADDED "+method); 653 } 654 } 655 //System.out.println("Matching methods = "+dest); 656 } catch (Exception e) { 657 logger.error("filterMethodKeywords"+e); 658 } 659 } 660 661 //Log.trace("pointcut."+cli.getName(),expr+" MATCH "+result); 662 } 663 664 /** 665 * Tells wether a method is an adder of one a set of collections 666 * @param method the method to test 667 * @param collections the collection items. If null, it matches any 668 * collection. 669 * @return true if method is an adder of one of the collections 670 */ 671 static boolean isAdder(AbstractMethodItem method, Collection collections) { 672 if (!method.isAdder()) { 673 return false; 674 } else { 675 if (collections==null) { 676 return true; 677 } else { 678 CollectionItem[] added = method.getAddedCollections(); 679 if (added!=null) { 680 for (int i=0; i<added.length; i++) { 681 if (collections.contains(added[i])) 682 return true; 683 } 684 } 685 return false; 686 } 687 } 688 } 689 690 /** 691 * Tells wether a method is a remover of one a set of collections 692 * @param method the method to test 693 * @param collections the collection items. If null, it matches any 694 * collection. 695 * @return true if method is a remover of one of the collections 696 */ 697 static boolean isRemover(AbstractMethodItem method, Collection collections) { 698 if (!method.isRemover()) { 699 return false; 700 } else { 701 if (collections==null) { 702 return true; 703 } else { 704 CollectionItem[] removed = method.getRemovedCollections(); 705 if (removed!=null) { 706 for (int i=0; i<removed.length; i++) { 707 if (collections.contains(removed[i])) 708 return true; 709 } 710 } 711 return false; 712 } 713 } 714 } 715 716 717 /** 718 * Tells wether a method is a writer of one a set of fields 719 * @param method the method to test 720 * @param fields the field items. If null, it matches any 721 * field 722 * @return true if method is a writer of one of the fields 723 */ 724 static boolean isWriter(AbstractMethodItem method, Collection fields) { 725 if (!method.isWriter()) { 726 return false; 727 } else { 728 if (fields==null) { 729 return true; 730 } else { 731 FieldItem[] written = method.getWrittenFields(); 732 if (written!=null) { 733 for (int i=0; i<written.length; i++) { 734 if (fields.contains(written[i])) 735 return true; 736 } 737 } 738 return false; 739 } 740 } 741 } 742 743 /** 744 * Tells wether a method is the setter of one of a set of fields 745 * @param method the method to test 746 * @param fields the field items. If null, it matches any 747 * field 748 * @return true if method is the setter of one of the fields 749 */ 750 static boolean isSetter(AbstractMethodItem method, Collection fields) { 751 FieldItem setField = method.getSetField(); 752 return (fields==null && setField!=null) || 753 (fields!=null && fields.contains(setField)); 754 } 755 756 757 /** 758 * Tells wether a method is the getter of one of a set of fields 759 * @param method the method to test 760 * @param fields the field items. If null, it matches any 761 * field 762 * @return true if method is the getter of one of the fields 763 */ 764 static boolean isGetter(AbstractMethodItem method, Collection fields) { 765 if (method instanceof MethodItem) { 766 FieldItem setField = ((MethodItem)method).getReturnedField(); 767 return (fields==null && setField!=null) || 768 (fields!=null && fields.contains(setField)); 769 } else { 770 return false; 771 } 772 } 773 774 /** 775 * Tells wether a method is a refaccessor of one a set of references 776 * @param method the method to test 777 * @param collections the reference items. If null, it matches any 778 * reference. 779 * @return true if method is a reference accessor of one of the references 780 */ 781 static boolean isReferenceAccessor(AbstractMethodItem method, Collection references) { 782 if (!method.isReferenceAccessor()) { 783 return false; 784 } else { 785 if (references==null) { 786 return true; 787 } else { 788 FieldItem[] refs = method.getAccessedReferences(); 789 if (refs!=null) { 790 for (int i=0; i<refs.length; i++) { 791 if (references.contains(refs[i])) 792 return true; 793 } 794 } 795 return false; 796 } 797 } 798 } 799 800 static boolean isCollectionAccessor(AbstractMethodItem method, Collection collections) { 801 if (!method.isCollectionAccessor()) { 802 return false; 803 } else { 804 if (collections==null) { 805 return true; 806 } else { 807 CollectionItem[] accessedCollections = method.getAccessedCollections(); 808 if (accessedCollections!=null) { 809 for (int i=0; i<accessedCollections.length; i++) { 810 if (collections.contains(accessedCollections[i])) 811 return true; 812 } 813 } 814 return false; 815 } 816 } 817 } 818 819 static boolean isCollectionModifier(AbstractMethodItem method, Collection collections) { 820 if (!method.hasModifiedCollections()) { 821 return false; 822 } else { 823 if (collections==null) { 824 return true; 825 } else { 826 CollectionItem[] modifiedCollections = method.getModifiedCollections(); 827 if (modifiedCollections!=null) { 828 for (int i=0; i<modifiedCollections.length; i++) { 829 if (collections.contains(modifiedCollections[i])) 830 return true; 831 } 832 } 833 return false; 834 } 835 } 836 } 837 838 Hashtable classCache = new Hashtable(); 839 840 /** 841 * Tests if the given component class is modified (in a way or 842 * another) by this pointcut. 843 * 844 * @param wrappee the component to test 845 * @return true if the class matches */ 846 847 public boolean isClassMatching(Wrappee wrappee, ClassItem cl) { 848 if (allClasses) 849 return true; 850 /* 851 Log.trace("pointcut.class",2, 852 "getting matching class for "+ 853 cl+"("+NameRepository.get().getName(wrappee)+") for "+ 854 wrappeeClassExpr+"/"+wrappingClassName+"."+wrappingMethodName); 855 */ 856 String className = null; 857 if (cl==null) { 858 cl = ClassRepository.get().getClass(wrappee); 859 } 860 className = cl.getName(); 861 862 Boolean match = (Boolean)classCache.get(className); 863 if (match==null) { 864 Iterator it = wrappeeClassRegexps.iterator(); 865 Iterator iti = iwrappeeClassExprs.iterator(); 866 try { 867 match = Boolean.TRUE; 868 while (it.hasNext()) { 869 RE regexp = (RE)it.next(); 870 boolean inv = ((Boolean) iti.next()).booleanValue(); 871 /* 872 Log.trace("pointcut.class",2, 873 "isClassMatching: comparing "+className+" with "+ 874 regexp+" (inv="+inv+")"); 875 */ 876 if (cl.isSubClassOf(regexp) ^ inv) { 877 /* 878 Log.trace("pointcut.class","Class "+className+" does not match "+ 879 (inv?"":"!")+regexp); 880 */ 881 match = Boolean.FALSE; 882 break; 883 } 884 } 885 } catch(Exception e) { 886 e.printStackTrace(); 887 } 888 classCache.put(className,match); 889 /* 890 if (match.booleanValue()) 891 Log.trace("pointcut.class","Class "+className+" matches "+ 892 wrappeeClassExprs+iwrappeeClassExprs); 893 */ 894 } else { 895 /* 896 Log.trace("pointcut.class",2,"class cache hit for "+ 897 cl+"("+NameRepository.get().getName(wrappee)+") -> "+match); 898 */ 899 } 900 return match.booleanValue(); 901 } 902 903 /** 904 * Tests if the given component is modified (in a way or 905 * another) by this pointcut. 906 * 907 * <p>Contrary to the class-based matching, this matching works on 908 * a per-component basis and not on a per-class basis. This is 909 * posible by using the naming aspect of the system (assuming it is 910 * there). If the naming aspect appears not to be woven, then all 911 * the components should mathes here. 912 * 913 * @param wrappee the component to test 914 * @return true if the name matches 915 */ 916 public boolean isNameMatching(Wrappee wrappee, ClassItem cl) { 917 if (allInstances) return true; 918 if (wrappee==null) return true; 919 String name = NameRepository.get().getName(wrappee); 920 if (name == null) { 921 //logger.info("Name "+name+" does not match "+wrappeeExprs); 922 return false; 923 } 924 return isNameMatching(wrappee,name); 925 } 926 927 public boolean isNameMatching(Wrappee wrappee,String name) { 928 loggerName.debug("isNameMatching "+name+","+wrappee); 929 Iterator it = wrappeeRegexps.iterator(); 930 Iterator it2 = wrappeeExprs.iterator(); 931 Iterator iti = iwrappeeExprs.iterator(); 932 try { 933 while (it.hasNext()) { 934 String s = (String)it2.next(); 935 if (isPathExpression(s)) { 936 boolean result = isInPath(wrappee,s); 937 /* 938 if (result) 939 logger.info("Name "+name+" matches "+wrappeeExprs); 940 else 941 logger.info("Name "+name+" does not match "+wrappeeExprs); 942 */ 943 return result; 944 } 945 RE regexp = (RE)it.next(); 946 boolean inv = ((Boolean)iti.next()).booleanValue(); 947 /* 948 Log.trace("wrap","isNameMatching: comparing "+name+" with "+ 949 regexp+" (inv="+inv); 950 */ 951 if (regexp.isMatch(name) ^ inv) { 952 /* 953 logger.info("Name "+name+" does not match "+ 954 (inv?"":"!")+s); 955 */ 956 return false; 957 } 958 } 959 } catch (Exception e) { 960 e.printStackTrace(); 961 } 962 return true; 963 } 964 965 /** 966 * Tells if this expression is a path expression (of the form o/r/o...). 967 * 968 * @param expr the expression to check 969 * @return true is a path expression */ 970 971 public static boolean isPathExpression(String expr) { 972 if (expr.indexOf('/') == -1) { 973 return false; 974 } else { 975 return true; 976 } 977 } 978 979 /** 980 * Tells if this object is reachable for the given object path 981 * expression. 982 * 983 * @param candidate the candidate object 984 * @param pathExpr the path expression 985 * @return boolean true if reachable */ 986 987 public static boolean isInPath(Object candidate, String pathExpr) { 988 StringTokenizer st = new StringTokenizer(pathExpr,"/"); 989 String root = st.nextToken(); 990 Collection accessible = NameRepository.getObjects(root); 991 try { 992 while (st.hasMoreTokens()) { 993 loggerPath.debug("intermediate accessibles are "+accessible); 994 String relExpr = st.nextToken(); 995 accessible = getRelatedObjects(accessible, relExpr); 996 String filterExpr = st.nextToken(); 997 accessible = filterObjects(accessible, filterExpr); 998 } 999 } catch( Exception e ) { 1000 loggerPath.error("malformed path expression: "+pathExpr,e); 1001 return false; 1002 } 1003 loggerPath.debug("checking if "+candidate+ 1004 " matches the path expression "+pathExpr+ 1005 ", accessible objects are: "+accessible); 1006 return accessible.contains( candidate ); 1007 } 1008 1009 /** 1010 * Gets all the objects that are related to a set of objects through 1011 * relations that match the given expression. 1012 * 1013 * @param initial the initial objects 1014 * @param relationExpr the expression that matches the relations 1015 * @return a set of accessible objects */ 1016 1017 public static Collection getRelatedObjects(Collection initial, 1018 String relationExpr) { 1019 loggerPath.debug("getRelatedObjects "+relationExpr); 1020 Iterator it = initial.iterator(); 1021 ClassRepository cr = ClassRepository.get(); 1022 Vector res = new Vector(); 1023 while( it.hasNext() ) { 1024 Object o = it.next(); 1025 ClassItem cli = cr.getClass(o.getClass()); 1026 loggerPath.debug("getting matching relations"); 1027 Collection rels = cli.getMatchingRelations(relationExpr); 1028 loggerPath.debug("matching relations are "+rels); 1029 Iterator itRels = rels.iterator(); 1030 while( itRels.hasNext() ) { 1031 FieldItem fi = (FieldItem) itRels.next(); 1032 if( fi.isReference() ) { 1033 Object ref = fi.get(o); 1034 if( ref != null ) { 1035 loggerPath.debug("adding referenced object "+ref); 1036 res.add(ref); 1037 } 1038 } else if (fi instanceof CollectionItem) { 1039 Collection cref = ((CollectionItem)fi).getActualCollection(o); 1040 if (cref != null) { 1041 loggerPath.debug("adding objects in collection"); 1042 res.addAll(cref); 1043 } 1044 } 1045 loggerPath.debug("performing next relation"); 1046 } 1047 } 1048 return res; 1049 } 1050 1051 /** 1052 * Filters a collection of objects regarding an expression. 1053 * 1054 * @param initial the collection to filter 1055 * @param filter the filtering expression 1056 * @return a filtered collection that contains only the objects 1057 * that match the expression */ 1058 1059 public static Collection filterObjects(Collection initial, 1060 String filter) { 1061 loggerPath.debug("filterObjects "+initial+"/"+filter); 1062 NameRepository nr = (NameRepository)NameRepository.get(); 1063 Vector res = new Vector(); 1064 try { 1065 // path expression allows the user to denote objects 1066 // with their index in the collection 1067 Integer index = new Integer(filter); 1068 res.add(((Vector)initial).get(index.intValue())); 1069 return res; 1070 } catch (Exception e) {} 1071 1072 if (initial==null) 1073 return res; 1074 Iterator it = initial.iterator(); 1075 try { 1076 RE regexp = new RE(filter, 0, RESyntax.RE_SYNTAX_EMACS); 1077 while(it.hasNext()) { 1078 Object o = it.next(); 1079 String name = nr.getName(o); 1080 if (name!=null && regexp.isMatch(name)) { 1081 res.add(o); 1082 } 1083 } 1084 } catch( Exception e ) { 1085 e.printStackTrace(); 1086 } 1087 return res; 1088 } 1089 1090 Hashtable hostCache = new Hashtable(); 1091 1092 /** 1093 * Tests if the given component is modified (in a way or 1094 * another) by this pointcut. 1095 * 1096 * <p>Contrary to the class-based matching, this matching works on 1097 * a per-component basis and not on a per-class basis. This is 1098 * posible by using the naming aspect of the system (assuming it is 1099 * there). If the naming aspect appears not to be woven, then all 1100 * the components should mathes here. 1101 * 1102 * @param wrappee the component to test 1103 * @param cl the class, in case of static method 1104 * @return true if the name matches 1105 */ 1106 public boolean isHostMatching(Wrappee wrappee, ClassItem cl) { 1107 if (allHosts) { return true; } 1108 String name = ""; 1109 try { 1110 Class distd = Class.forName("org.objectweb.jac.core.dist.Distd"); 1111 name = (String)distd.getMethod("getLocalContainerName",ExtArrays.emptyClassArray) 1112 .invoke(null,ExtArrays.emptyObjectArray); 1113 } catch (Exception e) { 1114 e.printStackTrace(); 1115 return false; 1116 } 1117 if (name == null) { 1118 loggerHost.debug("Host "+name+" does not match "+ 1119 hostExprs+ihostExprs); 1120 return false; 1121 } 1122 1123 Boolean match = (Boolean)hostCache.get(name); 1124 if (match==null) { 1125 Iterator it = hostRegexps.iterator(); 1126 Iterator iti = ihostExprs.iterator(); 1127 match = Boolean.TRUE; 1128 try { 1129 while(it.hasNext()) { 1130 RE regexp = (RE)it.next(); 1131 boolean inv = ((Boolean) iti.next()).booleanValue(); 1132 loggerHost.debug("isHostMatching: comparing "+name+" with "+ 1133 regexp+" (inv="+inv); 1134 if (regexp.isMatch(name) ^ inv) { 1135 loggerHost.debug("Host "+name+" does not match "+ 1136 (inv?"":"!")+regexp); 1137 match = Boolean.FALSE; 1138 break; 1139 } 1140 } 1141 } catch( Exception e ) { 1142 e.printStackTrace(); 1143 } 1144 hostCache.put(name,match); 1145 } 1146 loggerHost.debug("Host \""+name+"\" is "+(match.booleanValue()?"":" not ")+ 1147 "matching "+hostExprs+ihostExprs); 1148 return match.booleanValue(); 1149 } 1150 1151 /** 1152 * Tests if the given method item is modified (in a way or 1153 * another) by this pointcut. 1154 * 1155 * @param method the method to test 1156 * @return true if the method matches */ 1157 1158 public boolean isMethodMatching(AbstractMethodItem method) { 1159 Iterator it = wrappeeMethodRegexps.iterator(); 1160 Iterator iti = iwrappeeMethodExprs.iterator(); 1161 String name = method.toString(); 1162 try { 1163 while (it.hasNext()) { 1164 RE regexp = (RE)it.next(); 1165 boolean inv = ((Boolean)iti.next()).booleanValue(); 1166 /* 1167 Log.trace("pointcut."+method.getParent(), 1168 "isMethodMatching: comparing "+name+" with "+ 1169 regexp+" (inv="+inv+")"); 1170 */ 1171 if (regexp.isMatch(name) ^ inv) { 1172 /* 1173 Log.trace("pointcut."+method.getParent(), 1174 "Method "+name+" does not match "+ 1175 (inv?"":"!")+regexp); 1176 */ 1177 return false; 1178 } 1179 } 1180 } catch (Exception e) { 1181 e.printStackTrace(); 1182 } 1183 1184 /* 1185 Log.trace("pointcut."+method.getParent(),"Method "+name+" matches "+ 1186 wrappeeMethodExprs+iwrappeeMethodExprs); 1187 */ 1188 return true; 1189 } 1190 1191 FieldItem parameterToField(ClassItem cli, Object parameter) { 1192 if (parameter instanceof FieldItem) 1193 return (FieldItem)parameter; 1194 else if (parameter instanceof String) { 1195 return cli.getField((String)parameter); 1196 } 1197 else { 1198 logger.warn("Unknown parameter type "+ 1199 parameter.getClass().getName()); 1200 return null; 1201 } 1202 } 1203 1204 boolean isNoneParameter(Object parameter) { 1205 if (parameter instanceof String && 1206 "#NONE#".equals((String)parameter)) { 1207 return true; 1208 } 1209 return false; 1210 } 1211 1212 protected String parseKeyword(Wrappee wrappee, ClassItem cli, 1213 String keywordExpr, 1214 List parameters) { 1215 1216 loggerKeywords.debug("parseKeyword("+wrappee+","+cli+","+ 1217 keywordExpr+","+parameters+")"); 1218 String result = ""; 1219 1220 // replace attributes (<>) with the actual member values 1221 parameters = replaceTags(parameters,cli); 1222 1223 if (keywordExpr.equals("ALL")) { 1224 loggerKeywords.debug("found ALL keyword"); 1225 result = ".*"; 1226 1227 } else if (keywordExpr.equals("COLLECTIONS")) { 1228 loggerKeywords.debug("found COLLECTIONS keyword"); 1229 result = "org.objectweb.jac.lib.java.util.*"; 1230 1231 } 1232 1233 loggerKeywords.debug("parsed keyword "+keywordExpr+" => "+result); 1234 1235 return result; 1236 1237 } 1238 1239 static String quoteString(String string) { 1240 return Strings.replace(string,"[]","\\[\\]"); 1241 } 1242 1243 }