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.env.overload.Overload;
    19  import org.as2lib.env.except.AbstractOperationException;
    20  import org.as2lib.aop.Pointcut;
    21  import org.as2lib.aop.Aspect;
    22  import org.as2lib.aop.JoinPoint;
    23  import org.as2lib.aop.AopConfig;
    24  import org.as2lib.aop.joinpoint.AbstractJoinPoint;
    25  import org.as2lib.env.reflect.MethodInfo;
    26  import org.as2lib.env.reflect.PropertyInfo;
    27  
    28  /**
    29   * {@code AbstractAdvice} implements methods commonly needed by {@link Adivce}
    30   * implementations.
    31   * 
    32   * @author Simon Wacker
    33   */
    34  class org.as2lib.aop.advice.AbstractAdvice extends BasicClass {
    35  	
    36  	/** Signifies a before advice. */
    37  	public static var BEFORE:Number = 0;
    38  	
    39  	/** Signifies an around advice. */
    40  	public static var AROUND:Number = 1;
    41  	
    42  	/** Signifies an after advice. */
    43  	public static var AFTER:Number = 2;
    44  	
    45  	/** Signifies an after returning advice. */
    46  	public static var AFTER_RETURNING:Number = 3;
    47  	
    48  	/** Signifies an after throwing advice. */
    49  	public static var AFTER_THROWING:Number = 4;
    50  	
    51  	/** The pointcut that is responsible for checking if a join point is captured by this advice. */
    52  	private var pointcut:Pointcut;
    53  	
    54  	/** The aspect that contains this advice. */
    55  	private var aspect:Aspect;
    56  	
    57  	/**
    58  	 * Constructs a new {@code AbstractAdvice} instance.
    59  	 *
    60  	 * @param aspect (optional) the aspect that contains this advice
    61  	 */
    62  	private function AbstractAdvice(aspect:Aspect) {
    63  		this.aspect = aspect;
    64  	}
    65  	
    66  	/**
    67  	 * Returns a proxy method that can be used instead of the original method of the
    68  	 * {@code joinPoint}.
    69  	 * 
    70  	 * <p>The returned proxy invokes the abstract {@code executeJoinPoint} method of
    71  	 * this advice passing an update of the given {@code joinPoint} with the appropriate
    72  	 * logical this and the arguments used for the proxy invocation. Sub-classes are
    73  	 * responsible for implementing this method in the correct way.
    74  	 *
    75  	 * @param joinPoint the join point that represents the original method
    76  	 * @return the proxy method
    77  	 */
    78  	public function getProxy(joinPoint:JoinPoint):Function {
    79  		var owner:AbstractAdvice = this;
    80  		var result:Function = function() {
    81  			// MTASC doesn't allow access to private "executeJoinPoint"
    82  			return owner["executeJoinPoint"](joinPoint.update(this), arguments);
    83  		};
    84  		var method:Function;
    85  		if (joinPoint.getType() == AbstractJoinPoint.METHOD
    86  				|| joinPoint.getType() == AbstractJoinPoint.CONSTRUCTOR) {
    87  			method = MethodInfo(joinPoint.getInfo()).getMethod();
    88  		}
    89  		if (joinPoint.getType() == AbstractJoinPoint.SET_PROPERTY) {
    90  			method = PropertyInfo(joinPoint.getInfo()).getSetter().getMethod();
    91  		}
    92  		if (joinPoint.getType() == AbstractJoinPoint.GET_PROPERTY) {
    93  			method = PropertyInfo(joinPoint.getInfo()).getGetter().getMethod();
    94  		}
    95  		if (method) {
    96  			result.__proto__ = method.__proto__;
    97  			result.prototype = method.prototype;
    98  			result.__constructor__ = method.__constructor__;
    99  			result.constructor = method.constructor;
   100  			// just in case that any state is held in the original method, for classes this
   101  			// may be static variables, methods or properties
   102  			result.__resolve = function(name:String) {
   103  				return method[name];
   104  			};
   105  			// guarantees that the class info for the original class this proxy overwrites
   106  			// can still be found
   107  			result.valueOf = function():Object {
   108  				return method.valueOf();
   109  			};
   110  		}
   111  		return result;
   112  	}
   113  	
   114  	/**
   115  	 * Executes the woven-in code and the join point.
   116  	 * 
   117  	 * @param joinPoint the reached join point
   118  	 * @param args the arguments that were originally passed-to the join point
   119  	 * @return the result to return to the invoker of the given {@code joinPoint}
   120  	 * @throws AbstractOperationException always, because this is an abstract method
   121  	 * that must be overridden by sub-classes
   122  	 */
   123  	private function executeJoinPoint(joinPoint:JoinPoint, args:Array) {
   124  		throw new AbstractOperationException("This method is marked as abstract and must be overwritten.", this, arguments);
   125  	}
   126  	
   127  	/**
   128  	 * Sets the aspect that contains this advice.
   129  	 *
   130  	 * @param aspect the new aspect containing this advice
   131  	 */
   132  	private function setAspect(aspect:Aspect):Void {
   133  		this.aspect = aspect;
   134  	}
   135  	
   136  	/**
   137  	 * Returns the aspect that contains this advice.
   138  	 * 
   139  	 * @return the aspect that contains this advice
   140  	 */
   141  	public function getAspect(Void):Aspect {
   142  		return this.aspect;
   143  	}
   144  	
   145  	/**
   146  	 * @overload #setPointcutByPointcut
   147  	 * @overload #setPointcutByString
   148  	 */
   149  	private function setPointcut() {
   150  		var overload:Overload = new Overload(this);
   151  		overload.addHandler([Pointcut], setPointcutByPointcut);
   152  		overload.addHandler([String], setPointcutByString);
   153  		return overload.forward(arguments);
   154  	}
   155  	
   156  	/**
   157  	 * Sets a new pointcut. The pointcut determines which join points are captured.
   158  	 *
   159  	 * @param pointcut the new pointcut to set
   160  	 */
   161  	private function setPointcutByPointcut(pointcut:Pointcut):Void {
   162  		this.pointcut = pointcut;
   163  	}
   164  	
   165  	/**
   166  	 * Sets the new pointcut by the pointcut's string representation.
   167  	 *
   168  	 * @param pointcut the string representation of the pointcut
   169  	 * @return the actual pointcut set that was created by the given {@code pointcut}
   170  	 * string
   171  	 */
   172  	private function setPointcutByString(pointcut:String):Pointcut {
   173  		var result:Pointcut = AopConfig.getPointcutFactory().getPointcut(pointcut);
   174  		setPointcutByPointcut(result);
   175  		return result;
   176  	}
   177  	
   178  	/**
   179  	 * Returns the set pointcut.
   180  	 * 
   181  	 * @return the set pointcut
   182  	 */
   183  	public function getPointcut(Void):Pointcut {
   184  		return this.pointcut;
   185  	}
   186  	
   187  	/**
   188  	 * Checks whether this advice captures the given {@code joinPoint}. This check is
   189  	 * done with the help of the set pointcut's {@code captures} method.
   190  	 * 
   191  	 * <p>If there is no pointcut set, {@code true} will be returned.
   192  	 * 
   193  	 * @param joinPoint the join point upon which to make the check
   194  	 * @return {@code true} if the given {@code joinPoint} is captured else {@code false}
   195  	 */
   196  	public function captures(joinPoint:JoinPoint):Boolean {
   197  		if (!this.pointcut) return true;
   198  		return this.pointcut.captures(joinPoint);
   199  	}
   200  	
   201  }