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.aop.Weaver;
    19  import org.as2lib.env.overload.Overload;
    20  import org.as2lib.aop.Aspect;
    21  import org.as2lib.env.reflect.PackageInfo;
    22  import org.as2lib.env.reflect.ClassInfo;
    23  import org.as2lib.env.reflect.PropertyInfo;
    24  import org.as2lib.env.reflect.TypeMemberInfo;
    25  import org.as2lib.aop.JoinPoint;
    26  import org.as2lib.aop.joinpoint.MethodJoinPoint;
    27  import org.as2lib.aop.joinpoint.GetPropertyJoinPoint;
    28  import org.as2lib.aop.joinpoint.SetPropertyJoinPoint;
    29  import org.as2lib.aop.Advice;
    30  import org.as2lib.data.holder.map.HashMap;
    31  import org.as2lib.data.holder.Map;
    32  import org.as2lib.env.reflect.MethodInfo;
    33  import org.as2lib.env.reflect.ConstructorInfo;
    34  import org.as2lib.aop.joinpoint.ConstructorJoinPoint;
    35  import org.as2lib.aop.joinpoint.AbstractJoinPoint;
    36  import org.as2lib.util.ArrayUtil;
    37  
    38  /**
    39   * {@code SimpleWeaver} is a simple implementation of the {@code Weaver} interface that
    40   * supports most needed functionalities.
    41   * 
    42   * @author Simon Wacker
    43   */
    44  class org.as2lib.aop.weaver.SimpleWeaver extends BasicClass implements Weaver {
    45  	
    46  	/** Arrays of {@link Advice} instances that are mapped to affected {@link ClassInfo} instances. */
    47  	private var advices:Map;
    48  	
    49  	/**
    50  	 * Constructs a new {@code SimpleWeaver} instance.
    51  	 */
    52  	public function SimpleWeaver(Void) {
    53  		advices = new HashMap();
    54  	}
    55  	
    56  	/**
    57  	 * Weaves the added aspects and advices into the affected types.
    58  	 */
    59  	public function weave(Void):Void {
    60  		var affectedTypes:Array = advices.getKeys();
    61  		for (var i:Number = 0; i < affectedTypes.length; i++) {
    62  			var affectedType:ClassInfo = affectedTypes[i];
    63  			var affectedAdvices:Array = advices.get(affectedType);
    64  			if (affectedType) {
    65  				weaveByTypeAndAdvices(affectedType, affectedAdvices);
    66  			} else {
    67  				weaveByPackageAndAdvices(PackageInfo.getRootPackage(), affectedAdvices);
    68  			}
    69  		}
    70  	}
    71  	
    72  	private function weaveByPackageAndAdvices(package:PackageInfo, advices:Array):Void {
    73  		if (package) {
    74  			if (advices) {
    75  				var classes:Array = package.getMemberClasses(false);
    76  				if (classes) {
    77  					for (var i:Number = 0; i < classes.length; i++) {
    78  						var clazz:ClassInfo = ClassInfo(classes[i]);
    79  						if (clazz) {
    80  							weaveByTypeAndAdvices(clazz, advices);
    81  						}
    82  					}
    83  				}
    84  			}
    85  		}
    86  	}
    87  	
    88  	private function weaveByTypeAndAdvices(type:ClassInfo, advices:Array):Void {
    89  		if (type) {
    90  			var constructor:ConstructorInfo = type.getConstructor();
    91  			if (constructor) {
    92  				weaveByJoinPointAndAdvices(new ConstructorJoinPoint(constructor, null), advices);
    93  			}
    94  			var prototype:Object = type.getType().prototype;
    95  			if (prototype.__constructor__) {
    96  				// getType().valueOf() is important because valueOf ensures that not a proxy but the original constructor is used
    97  				var superClassConstructor:ConstructorInfo = new ConstructorInfo(ClassInfo(type.getSuperType()), Function(type.getSuperType().getType().valueOf()));
    98  				// there is a bug with the __constructor__ variable, we fix this by assigning
    99  				// this variable by hand to the correct method
   100  				prototype.__constructor__ = superClassConstructor.getMethod();
   101  				if (this.advices.containsKey(superClassConstructor.getDeclaringType())) {
   102  					weaveSuperClassConstructor(new ConstructorJoinPoint(superClassConstructor, null), prototype, this.advices.get(superClassConstructor.getDeclaringType()));
   103  					//weaveBySuperClassConstructorJoinPointAndAdvices(, advices.get(superClassConstructor.getMethod()));
   104  				}
   105  			}
   106  			var methods:Array = type.getMethodsByFlag(true);
   107  			if (methods) {
   108  				for (var i:Number = 0; i < methods.length; i++) {
   109  					var method:MethodInfo = MethodInfo(methods[i]);
   110  					if (method) {
   111  						weaveByJoinPointAndAdvices(new MethodJoinPoint(method, null), advices);
   112  					}
   113  				}
   114  			}
   115  			var properties:Array = type.getPropertiesByFlag(true);
   116  			if (properties) {
   117  				for (var i:Number = 0; i < properties.length; i++) {
   118  					var property:PropertyInfo = PropertyInfo(properties[i]);
   119  					if (property) {
   120  						weaveByJoinPointAndAdvices(new GetPropertyJoinPoint(property, null), advices);
   121  						weaveByJoinPointAndAdvices(new SetPropertyJoinPoint(property, null), advices);
   122  					}
   123  				}
   124  			}
   125  		}
   126  	}
   127  	
   128  	private function weaveSuperClassConstructor(superClassConstructorJoinPoint:ConstructorJoinPoint, prototype, advices:Array):Void {
   129  		if (prototype && advices) {
   130  			for (var i:Number = 0; i < advices.length; i++) {
   131  				var advice:Advice = Advice(advices[i]);
   132  				if (advice) {
   133  					if (advice.captures(superClassConstructorJoinPoint)) {
   134  						// TODO refactor
   135  						// not ' = snapshot()' because this makes a snapshot of the constructor in the package and not in the prototype
   136  						var c:ConstructorInfo = new ConstructorInfo(ClassInfo(superClassConstructorJoinPoint.getInfo().getDeclaringType()), prototype.__constructor__);
   137  						prototype.__constructor__ = advice.getProxy(new ConstructorJoinPoint(c, null));
   138  					}
   139  				}
   140  			}
   141  		}
   142  	}
   143  	
   144  	private function weaveByJoinPointAndAdvices(joinPoint:JoinPoint, advices:Array):Void {
   145  		if (joinPoint) {
   146  			if (advices) {
   147  				for (var i:Number = 0; i < advices.length; i++) {
   148  					var advice:Advice = Advice(advices[i]);
   149  					if (advice) {
   150  						if (advice.captures(joinPoint)) {
   151  							weaveByJoinPointAndAdvice(joinPoint.snapshot(), advice);
   152  						}
   153  					}
   154  				}
   155  			}
   156  		}
   157  	}
   158  	
   159  	private function weaveByJoinPointAndAdvice(joinPoint:JoinPoint, advice:Advice):Void {
   160  		var proxy:Function = advice.getProxy(joinPoint);
   161  		var info:TypeMemberInfo = joinPoint.getInfo();
   162  		if (joinPoint.getType() == AbstractJoinPoint.CONSTRUCTOR) {
   163  			info.getDeclaringType().getPackage().getPackage()[info.getDeclaringType().getName()] = proxy;
   164  		} else {
   165  			if (info.isStatic()) {
   166  				info.getDeclaringType().getType()[info.getName()] = proxy;
   167  			} else {
   168  				info.getDeclaringType().getType().prototype[info.getName()] = proxy;
   169  			}
   170  		}
   171  	}
   172  	
   173  	/**
   174  	 * @overload #addAspectForAllTypes
   175  	 * @overload #addAspectForAllTypesInPackage
   176  	 * @overload #addAspectForMultipleAffectedTypes
   177  	 * @overload #addAspectForOneAffectedType
   178  	 */
   179  	public function addAspect():Void {
   180  		var o:Overload = new Overload(this);
   181  		o.addHandler([Aspect], addAspectForAllTypes);
   182  		o.addHandler([Aspect, Object], addAspectForAllTypesInPackage);
   183  		o.addHandler([Aspect, Array], addAspectForMultipleAffectedTypes);
   184  		o.addHandler([Aspect, Function], addAspectForOneAffectedType);
   185  		o.forward(arguments);
   186  	}
   187  	
   188  	/**
   189  	 * Adds the given {@code aspect} for all types. This means that all types are
   190  	 * searched through starting from the default or root package and checked whether
   191  	 * their join points match any of the advices of the {@code aspect}.
   192  	 * 
   193  	 * @param aspect the aspect whose advices shall be woven-into captured join points
   194  	 */
   195  	public function addAspectForAllTypes(aspect:Aspect):Void {
   196  		if (aspect) {
   197  			addAspectForAllTypesInPackage(aspect, PackageInfo.getRootPackage());
   198  		}
   199  	}
   200  	
   201  	/**
   202  	 * Adds the given {@code aspect} for the types that are directly members of the
   203  	 * given {@code affectedPackage} or any sub-package. All these types are regarded
   204  	 * as affected types that are searched through for matching join points.
   205  	 * 
   206  	 * @param aspect the aspect whose advices shall be woven-into captured join points
   207  	 * @param affectedPackage the package to search for matching join points
   208  	 * @throws IllegalArgumentException if {@code affectedPackage} is {@code null} or
   209  	 * {@code undefined}
   210  	 */
   211  	public function addAspectForAllTypesInPackage(aspect:Aspect, affectedPackage:Object):Void {
   212  		if (aspect) {
   213  			if (affectedPackage !== null && affectedPackage !== undefined) {
   214  				var packageInfo:PackageInfo = PackageInfo.forPackage(affectedPackage);
   215  				if (packageInfo) {
   216  					var classes:Array = packageInfo.getMemberClasses(false);
   217  					for (var i:Number = 0; i < classes.length; i++) {
   218  						var clazz:ClassInfo = ClassInfo(classes[i]);
   219  						if (clazz) {
   220  							addAspectForOneAffectedType(aspect, clazz.getType());
   221  						}
   222  					}
   223  				}
   224  			}
   225  		}
   226  	}
   227  	
   228  	/**
   229  	 * Adds the given {@code aspect} for the types that are contained in
   230  	 * {@code affectedTypes}. The {@code affectedTypes} array is supposed to hold
   231  	 * elements of type {@code Function}.
   232  	 * 
   233  	 * @param aspect the aspect whose advices shall be woven-into captured join points
   234  	 * @param affectedType a list of affected types
   235  	 */
   236  	public function addAspectForMultipleAffectedTypes(aspect:Aspect, affectedTypes:Array):Void {
   237  		if (aspect) {
   238  			if (affectedTypes) {
   239  				for (var i:Number = 0; i < affectedTypes.length; i++) {
   240  					var affectedType:Function = Function(affectedTypes[i]);
   241  					if (affectedType) {
   242  						addAspectForOneAffectedType(aspect, affectedType);
   243  					}
   244  				}
   245  			}
   246  		}
   247  	}
   248  	
   249  	/**
   250  	 * Adds the given {@code aspect} for the given {@code affectedType}. Only the given
   251  	 * {@code affectedType} is searched through when searching for join points that may
   252  	 * match the advices of the given {@code aspect}.
   253  	 * 
   254  	 * @param aspect the aspect whose advices shall be woven-into captured join points
   255  	 * @param affectedType the affected type to search for join points
   256  	 */
   257  	public function addAspectForOneAffectedType(aspect:Aspect, affectedType:Function):Void {
   258  		if (aspect) {
   259  			if (affectedType) {
   260  				var advices:Array = aspect.getAdvices();
   261  				if (advices) {
   262  					for (var i:Number = 0; i < advices.length; i++) {
   263  						var advice:Advice = Advice(advices[i]);
   264  						if (advice) {
   265  							addAdviceForOneAffectedType(advice, affectedType);
   266  						}
   267  					}
   268  				}
   269  			}
   270  		}
   271  	}
   272  	
   273  	/**
   274  	 * @overload #addAdviceForAllTypes
   275  	 * @overload #addAdviceForAllTypesInPackage
   276  	 * @overload #addAdviceForMultipleAffectedTypes
   277  	 * @overload #addAdviceForOneAffectedType
   278  	 */
   279  	public function addAdvice():Void {
   280  		var o:Overload = new Overload(this);
   281  		o.addHandler([Advice], addAdviceForAllTypes);
   282  		o.addHandler([Advice, Object], addAdviceForAllTypesInPackage);
   283  		o.addHandler([Advice, Array], addAdviceForMultipleAffectedTypes);
   284  		o.addHandler([Advice, Function], addAdviceForOneAffectedType);
   285  		o.forward(arguments);
   286  	}
   287  	
   288  	/**
   289  	 * Adds the given {@code advice} for all types. This means that all types are
   290  	 * searched through starting from the default or root package and checked whether
   291  	 * their join points match any of the advices of the {@code advice}.
   292  	 * 
   293  	 * @param advice the advice to weave-into matching join points
   294  	 */
   295  	public function addAdviceForAllTypes(advice:Advice):Void {
   296  		if (advice) {
   297  			addAdviceForAllTypesInPackage(advice, PackageInfo.getRootPackage());
   298  		}
   299  	}
   300  	
   301  	/**
   302  	 * Adds the given {@code advice} for the types that are directly members of the
   303  	 * given {@code affectedPackage} or any sub-package. All these types are regarded
   304  	 * as affected types that are searched through for matching join points.
   305  	 * 
   306  	 * @param advice the advice to weave-into captured join points
   307  	 * @param affectedPackage the package to search for matching join points
   308  	 */
   309  	public function addAdviceForAllTypesInPackage(advice:Advice, package:Object):Void {
   310  		if (advice) {
   311  			if (package !== null && package !== undefined) {
   312  				var packageInfo:PackageInfo = PackageInfo.forPackage(package);
   313  				if (packageInfo) {
   314  					var classes:Array = packageInfo.getMemberClasses(false);
   315  					for (var i:Number = 0; i < classes.length; i++) {
   316  						var clazz:ClassInfo = ClassInfo(classes[i]);
   317  						if (clazz) {
   318  							addAdviceForOneAffectedType(advice, clazz.getType());
   319  						}
   320  					}
   321  				}
   322  			}
   323  		}
   324  	}
   325  	
   326  	/**
   327  	 * Adds the given {@code advice} for the types that are contained in
   328  	 * {@code affectedTypes}. The {@code affectedTypes} array is supposed to hold
   329  	 * elements of type {@code Function}.
   330  	 * 
   331  	 * @param advice the advice to weave-into captured join points
   332  	 * @param affectedType a list of affected types
   333  	 */
   334  	public function addAdviceForMultipleAffectedTypes(advice:Advice, affectedTypes:Array):Void {
   335  		if (advice) {
   336  			if (affectedTypes) {
   337  				for (var i:Number = 0; i < affectedTypes.length; i++) {
   338  					var affectedType:Function = Function(affectedTypes[i]);
   339  					if (affectedType) {
   340  						addAdviceForOneAffectedType(advice, affectedType);
   341  					}
   342  				}
   343  			}
   344  		}
   345  	}
   346  	
   347  	/**
   348  	 * Adds the given {@code advice} for the given {@code affectedType}. Only the given
   349  	 * {@code affectedType} is searched through when searching for join points that may
   350  	 * match the given {@code advice}.
   351  	 * 
   352  	 * @param advice the advice to weave-into captured join points
   353  	 * @param affectedType the affected type to search for join points
   354  	 */
   355  	public function addAdviceForOneAffectedType(advice:Advice, affectedType:Function):Void {
   356  		if (advice) {
   357  			var typeInfo:ClassInfo = null;
   358  			if (affectedType) {
   359  				typeInfo = ClassInfo.forClass(affectedType);
   360  			}
   361  			if (!advices.containsKey(typeInfo)) {
   362  				advices.put(typeInfo, new Array());
   363  			}
   364  			var affectedAdvices:Array = advices.get(typeInfo);
   365  			// TODO is this really always wanted? add a flag if not?
   366  			if (!ArrayUtil.contains(affectedAdvices, advice)) {
   367  				affectedAdvices.push(advice);
   368  			}
   369  			if (typeInfo.getSuperType()) {
   370  				addAdviceForOneAffectedType(advice, typeInfo.getSuperType().getType());
   371  			}
   372  		}
   373  	}
   374  	
   375  }