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    }