1  /*
     2   * Copyright the original author or authors.
     3   * 
     4   * Licensed under the MOZILLA PUBLIC LICENSE, Version 1.1 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   * 
     8   *      http://www.mozilla.org/MPL/MPL-1.1.html
     9   * 
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  import org.as2lib.core.BasicClass;
    18  import org.as2lib.util.StringUtil;
    19  import org.as2lib.env.except.IllegalArgumentException;
    20  import org.as2lib.data.holder.Map;
    21  import org.as2lib.data.holder.map.HashMap;
    22  import org.as2lib.aop.AopConfig;
    23  import org.as2lib.aop.Pointcut;
    24  import org.as2lib.aop.pointcut.OrPointcut;
    25  import org.as2lib.aop.pointcut.AndPointcut;
    26  import org.as2lib.aop.pointcut.NotPointcut;
    27  import org.as2lib.aop.pointcut.KindedPointcut;
    28  import org.as2lib.aop.pointcut.WithinPointcut;
    29  import org.as2lib.aop.pointcut.PointcutFactory;
    30  import org.as2lib.aop.pointcut.PointcutRule;
    31  import org.as2lib.aop.joinpoint.AbstractJoinPoint;
    32  
    33  /**
    34   * {@code DynamicPointcutFactory} is a pointcut factory that can be dynamically expanded
    35   * with new pointcut types at run-time. You can do so by adding a new pointcut factory.
    36   * This pointcut factory is mapped to a pointcut rule that determines whether the given
    37   * pointcut factory is used to create the pointcut to return based on a given pointcut
    38   * pattern.
    39   * 
    40   * <p>This pointcut factory allows for execution, within, set and get access join points
    41   * and for composite pointcuts combined with AND or OR logic.
    42   * <code>execution(org.as2lib.env.Logger.debug)</code>
    43   * <code>set(org.as2lib.MyClass.myProperty)</code>
    44   * <code>get(org.as2lib.MyClass.myProperty)</code>
    45   * <code>within(org.as2lib.MyClass)</code>
    46   * <code>execution(org.as2lib.env.Logger.debug) || set(org.as2lib.MyClass.myProperty)</code>
    47   * 
    48   * <p>Negation is also supported for every kind of pointcut:
    49   * <code>execution(org.as2lib..*.*) && !within(org.as2lib.MyAspect)</code>
    50   * 
    51   * <p>You may of course enhance the list of supported pointcuts by binding new ones with
    52   * the {@link #bindPointcutFactory} method.
    53   * 
    54   * @author Simon Wacker
    55   */
    56  class org.as2lib.aop.pointcut.DynamicPointcutFactory extends BasicClass implements PointcutFactory {
    57  	
    58  	/** All bound factories. */
    59  	private var factoryMap:Map;
    60  	
    61  	/**
    62  	 * Constructs a new {@code DynamicPointcutFactory} instance.
    63  	 */
    64  	public function DynamicPointcutFactory(Void) {
    65  		factoryMap = new HashMap();
    66  		bindOrPointcut();
    67  		bindAndPointcut();
    68  		bindNotPointcut();
    69  		bindExecutionPointcut();
    70  		bindSetPropertyPointcut();
    71  		bindGetPropertyPointcut();
    72  		bindWithinPointcut();
    73  	}
    74  	
    75  	/**
    76  	 * Returns a blank pointcut rule. This is a rule with no initialized methods.
    77  	 * 
    78  	 * @return a blank pointcut rule
    79  	 */
    80  	private function getBlankPointcutRule(Void):PointcutRule {
    81  		var result = new Object();
    82  		result.__proto__ = PointcutRule["prototype"];
    83  		result.__constructor__ = PointcutRule;
    84  		return result;
    85  	}
    86  	
    87  	/**
    88  	 * Returns a blank pointcut factory. That is a factory with no initialized methods.
    89  	 *
    90  	 * @return a blank pointcut factory
    91  	 */
    92  	private function getBlankPointcutFactory(Void):PointcutFactory {
    93  		var result = new Object();
    94  		result.__proto__ = PointcutFactory["prototype"];
    95  		result.__constructor__ = PointcutFactory;
    96  		return result;
    97  	}
    98  	
    99  	/**
   100  	 * TODO: Documentation
   101  	 */
   102  	private function bindOrPointcut(Void):Void {
   103  		var rule:PointcutRule = getBlankPointcutRule();
   104  		rule.execute = function(pattern:String):Boolean {
   105  			return (pattern.indexOf("||") != -1);
   106  		};
   107  		var factory:PointcutFactory = getBlankPointcutFactory();
   108  		factory.getPointcut = function(pattern:String):Pointcut {
   109  			return (new OrPointcut(pattern));
   110  		};
   111  		bindPointcutFactory(rule, factory);
   112  	}
   113  	
   114  	/**
   115  	 * TODO: Documentation
   116  	 */
   117  	private function bindAndPointcut(Void):Void {
   118  		var rule:PointcutRule = getBlankPointcutRule();
   119  		rule.execute = function(pattern:String):Boolean {
   120  			return (pattern.indexOf("&&") != -1);
   121  		};
   122  		var factory:PointcutFactory = getBlankPointcutFactory();
   123  		factory.getPointcut = function(pattern:String):Pointcut {
   124  			return (new AndPointcut(pattern));
   125  		};
   126  		bindPointcutFactory(rule, factory);
   127  	}
   128  	
   129  	/**
   130  	 * TODO: Documentation
   131  	 */
   132  	private function bindNotPointcut(Void):Void {
   133  		var rule:PointcutRule = getBlankPointcutRule();
   134  		rule.execute = function(pattern:String):Boolean {
   135  			return (pattern.indexOf("!") == 0);
   136  		};
   137  		var factory:PointcutFactory = getBlankPointcutFactory();
   138  		factory.getPointcut = function(pattern:String):Pointcut {
   139  			pattern = pattern.substring(1, pattern.length);
   140  			return (new NotPointcut(AopConfig.getPointcutFactory().getPointcut(pattern)));
   141  		};
   142  		bindPointcutFactory(rule, factory);
   143  	}
   144  	
   145  	/**
   146  	 * TODO: Documentation
   147  	 */
   148  	private function bindExecutionPointcut(Void):Void {
   149  		var rule:PointcutRule = getBlankPointcutRule();
   150  		rule.execute = function(pattern:String):Boolean {
   151  			return (pattern.indexOf("execution") == 0);
   152  		};
   153  		var factory:PointcutFactory = getBlankPointcutFactory();
   154  		factory.getPointcut = function(pattern:String):Pointcut {
   155  			if (pattern.indexOf("()") != -1) {
   156  				pattern = pattern.substring(10, pattern.length - 3);
   157  			} else {
   158  				pattern = pattern.substring(10, pattern.length - 1);
   159  			}
   160  			return (new KindedPointcut(pattern, AbstractJoinPoint.METHOD | AbstractJoinPoint.CONSTRUCTOR));
   161  		};
   162  		bindPointcutFactory(rule, factory);
   163  	}
   164  	
   165  	/**
   166  	 * TODO: Documentation
   167  	 */
   168  	private function bindSetPropertyPointcut(Void):Void {
   169  		var rule:PointcutRule = getBlankPointcutRule();
   170  		rule.execute = function(pattern:String):Boolean {
   171  			return (pattern.indexOf("set") == 0);
   172  		};
   173  		var factory:PointcutFactory = getBlankPointcutFactory();
   174  		factory.getPointcut = function(pattern:String):Pointcut {
   175  			pattern = pattern.substring(4, pattern.length - 1);
   176  			return (new KindedPointcut(pattern, AbstractJoinPoint.SET_PROPERTY));
   177  		};
   178  		bindPointcutFactory(rule, factory);
   179  	}
   180  	
   181  	/**
   182  	 * TODO: Documentation
   183  	 */
   184  	private function bindGetPropertyPointcut(Void):Void {
   185  		var rule:PointcutRule = getBlankPointcutRule();
   186  		rule.execute = function(pattern:String):Boolean {
   187  			return (pattern.indexOf("get") == 0);
   188  		};
   189  		var factory:PointcutFactory = getBlankPointcutFactory();
   190  		factory.getPointcut = function(pattern:String):Pointcut {
   191  			pattern = pattern.substring(4, pattern.length - 1);
   192  			return (new KindedPointcut(pattern, AbstractJoinPoint.GET_PROPERTY));
   193  		};
   194  		bindPointcutFactory(rule, factory);
   195  	}
   196  	
   197  	/**
   198  	 * TODO: Documentation
   199  	 */
   200  	private function bindWithinPointcut(Void):Void {
   201  		var rule:PointcutRule = getBlankPointcutRule();
   202  		rule.execute = function(pattern:String):Boolean {
   203  			return (pattern.indexOf("within") == 0);
   204  		};
   205  		var factory:PointcutFactory = getBlankPointcutFactory();
   206  		factory.getPointcut = function(pattern:String):Pointcut {
   207  			pattern = pattern.substring(7, pattern.length - 1);
   208  			return (new WithinPointcut(pattern));
   209  		};
   210  		bindPointcutFactory(rule, factory);
   211  	}
   212  	
   213  	/**
   214  	 * Returns a pointcut based on the passed-in {@code pattern} representation.
   215  	 * 
   216  	 * <p>The pointcut to return is determined by the rules of the added pointcut
   217  	 * factories. The pointcut factory whose rule applies first to the given
   218  	 * {@code pattern} is used to get the pointcut to return. This means that order
   219  	 * matters.
   220  	 *
   221  	 * @param pattern the string representation of the pointcut
   222  	 * @return the object-oriented view of the passed-in pointcut {@code pattern}
   223  	 */
   224  	public function getPointcut(pattern:String):Pointcut {
   225  		if (!pattern) return null;
   226  		// this should be refactored, it is not a perfect solution, but it works for now
   227  		if (pattern.indexOf(" ") != -1) {
   228  			pattern = StringUtil.trim(pattern);
   229  			while (pattern.indexOf("  ") != -1) {
   230  				pattern = StringUtil.replace(pattern, "  ", " ");
   231  			}
   232  			pattern = StringUtil.replace(pattern, "( ", "(");
   233  			pattern = StringUtil.replace(pattern, " )", ")");
   234  			pattern = StringUtil.replace(pattern, " (", "(");
   235  			pattern = StringUtil.replace(pattern, "! ", "!");
   236  			pattern = StringUtil.replace(pattern, " &&", "&&");
   237  			pattern = StringUtil.replace(pattern, " ||", "||");
   238  			pattern = StringUtil.replace(pattern, "&& ", "&&");
   239  			pattern = StringUtil.replace(pattern, "|| ", "||");
   240  		}
   241  		var rules:Array = factoryMap.getKeys();
   242  		var factories:Array = factoryMap.getValues();
   243  		for (var i:Number = 0; i < rules.length; i++) {
   244  			var rule:PointcutRule = rules[i];
   245  			if (rule.execute(pattern)) {
   246  				return PointcutFactory(factories[i]).getPointcut(pattern);
   247  			}
   248  		}
   249  		return null;
   250  	}
   251  	
   252  	/**
   253  	 * Binds a new {@code factory} to the given {@code rule}.
   254  	 *
   255  	 * @param rule the rule that must evaluate to {@code true} to indicate that the
   256  	 * {@code factory} shall be used for a given pointcut pattern
   257  	 * @param factory the factory to add
   258  	 * @throws IllegalArgumentException if rule is {@code null} or {@code undefined}
   259  	 * @throws IllegalArgumentException if factory is {@code null} or {@code undefined}
   260  	 */
   261  	public function bindPointcutFactory(rule:PointcutRule, factory:PointcutFactory):Void {
   262  		if (!rule || !factory) throw new IllegalArgumentException("Neither argument 'rule' nor argument 'factory' must be {@code null} nor {@code undefined}.", this, arguments);
   263  		factoryMap.put(rule, factory);
   264  	}
   265  	
   266  }