COM 3205 HW 2 Paul Freeman October 4, 2002 ================================================================================ PART 1.1: 30pts (unknows = 4, print statement = 2) Unknown 1: aspect Unknown 2: 0 Unknown 3: getThis Unknown 4: getTarget Unknown 5: thisObject == targetObject Unknown 6: targetObject the output here should be "call to static method" Unknown 7: getSourceLocation ================================================================================ PART 1.2: 50pts Modified code in the LoDPointcuts aspect: // maintain the stack of join points Stack callStack = new Stack(); Object around(): Execution() { if (!(thisJoinPoint.getThis() == thisJoinPoint.getTarget())) // an assertion System.out.println(" ============ should never happen "); nestingLevel++; // NOTE: nesting level == callStack.size callStack.push(thisJoinPoint); // print the call stack at this point of execution System.out.println("Call Stack (nested level = " + nestingLevel + "):"); System.out.println("- top args -"); JPUtil.toString((JoinPoint)callStack.peek()); System.out.println("---------"); for(int i = callStack.size() - 1; i >= 0; i--){ System.out.println(((JoinPoint)callStack.elementAt(i)).getSignature().toString()); } System.out.println("---------"); Object p = proceed(); callStack.pop(); nestingLevel--; return p; } Output: NOTE: in the following output, code was added so that the println statements are printed tabbed over so that it is easier to visualize the nested level. The following code was added to LoDPointcuts to facilitate this: // capture all println statements in this aspect and the JPUtil class pointcut NestedPrints(): call(void println(String)) && (within(LoDPointCuts) || within(JPUtil)); // print a number of tab characters = (nestingLevel - 1) before each println // statement in this aspect and the JPUtil class. Do this to help visualize // the nesting of calls before(): NestedPrints() { for(int i = 1; i < nestingLevel; i++){ System.out.print("\t"); } } ****************** ** begin output ** Call Stack (nested level = 1): - top args - begin arguments argument 0:[Ljava.lang.String;@19c082 end arguments --------- void lodchecker.Foo.main(String[]) --------- Call Stack (nested level = 2): - top args - begin arguments argument 0: end arguments --------- void lodchecker.Foo.run(Object) void lodchecker.Foo.main(String[]) --------- Call Stack (nested level = 3): - top args - begin arguments argument 0:1 argument 1:2 argument 2:3 end arguments --------- int lodchecker.Foo.add(int, int, int) void lodchecker.Foo.run(Object) void lodchecker.Foo.main(String[]) --------- *** OK, local call Call Stack (nested level = 3): - top args - begin arguments end arguments --------- String lodchecker.Foo.toString() void lodchecker.Foo.run(Object) void lodchecker.Foo.main(String[]) --------- Call Stack (nested level = 3): - top args - begin arguments end arguments --------- void lodchecker.Foo.test() void lodchecker.Foo.run(Object) void lodchecker.Foo.main(String[]) --------- Call Stack (nested level = 4): - top args - begin arguments end arguments --------- String lodchecker.Foo.toString() void lodchecker.Foo.test() void lodchecker.Foo.run(Object) void lodchecker.Foo.main(String[]) --------- Call Stack (nested level = 3): - top args - begin arguments end arguments --------- void lodchecker.Foo.bar() void lodchecker.Foo.run(Object) void lodchecker.Foo.main(String[]) --------- begin arguments end arguments >>> void lodchecker.Foo.bar() in Foo.java line 31(7) *** call to static method Call Stack (nested level = 3): - top args - begin arguments end arguments --------- Foo lodchecker.Foo.getF() void lodchecker.Foo.run(Object) void lodchecker.Foo.main(String[]) --------- *** OK, local call Call Stack (nested level = 3): - top args - begin arguments end arguments --------- Foo lodchecker.Foo.getF() void lodchecker.Foo.run(Object) void lodchecker.Foo.main(String[]) --------- Call Stack (nested level = 3): - top args - begin arguments end arguments --------- void lodchecker.Foo.test() void lodchecker.Foo.run(Object) void lodchecker.Foo.main(String[]) --------- Call Stack (nested level = 4): - top args - begin arguments end arguments --------- String lodchecker.Foo.toString() void lodchecker.Foo.test() void lodchecker.Foo.run(Object) void lodchecker.Foo.main(String[]) --------- Call Stack (nested level = 3): - top args - begin arguments end arguments --------- void lodchecker.Foo.test() void lodchecker.Foo.run(Object) void lodchecker.Foo.main(String[]) --------- Call Stack (nested level = 4): - top args - begin arguments end arguments --------- String lodchecker.Foo.toString() void lodchecker.Foo.test() void lodchecker.Foo.run(Object) void lodchecker.Foo.main(String[]) --------- Call Stack (nested level = 3): - top args - begin arguments end arguments --------- Object lodchecker.Foo.compute() void lodchecker.Foo.run(Object) void lodchecker.Foo.main(String[]) --------- *** OK, local call Call Stack (nested level = 3): - top args - begin arguments end arguments --------- Object lodchecker.Foo.compute() void lodchecker.Foo.run(Object) void lodchecker.Foo.main(String[]) --------- *** OK, local call Call Stack (nested level = 3): - top args - begin arguments end arguments --------- Object lodchecker.Foo.compute() void lodchecker.Foo.run(Object) void lodchecker.Foo.main(String[]) --------- *** OK, local call begin arguments argument 0: end arguments >>> void lodchecker.Foo.run(Object) in Foo.java line 41(7) *** call in static method Call Stack (nested level = 2): - top args - begin arguments end arguments --------- String lodchecker.Foo.toString() void lodchecker.Foo.main(String[]) --------- begin arguments end arguments >>> String lodchecker.Foo.toString() in Foo.java line 42(7) *** call in static method ================================================================================ PART 1.3: 10pts The DRY principle is violated is in the declaration of the pointcuts 'Execution' and 'Call'. In both declarations there occurs a repeat of pointcut designators. Adding aspects and classes to our implementation might neccesitate adding more designators to both of these pointcuts. Instead, a 'CodeExclusion' pointcut could be created, altering the code as follows: // exclude the following code pointcut CodeExclusion(): !within(LoDPointCuts) && !within(JPUtil) && !cflow(call (* JPUtil.*(..))); // consider all method executions pointcut Execution(): execution(* *(..)) && CodeExclusion(); // consider all method calls pointcut Call(): call(* *(..)) && CodeExclusion(); Now we have abstracted code exclusion to one location. Now further code exclusion join point designators will only have to be added to one pointcut, not two. NOTE: that the examination of the 'Call' and 'Execution' of a statement is not a violation of the DRY principle in and of itself. Call pointcuts occur at the location of the method call, while execution pointcuts occur at the location of the method execution. i.e. in the following program: 1 class A{ 2 void foo(){..}; 3 } 4 class B{ 5 void test(){ 6 A a = new A(); 7 a.foo(); 8 } 9 } the call of the method 'foo()' of class A occurs at line 7, while the execution of the method 'foo()' of class A occurs at line 2. Importantly, the following object equivalencies hold when using the methods: thisJoinPoint.getThis; thisJoinPoint.getTarget; used in a call join point: thisJoinPoint.getThis != thisJoinPoint.getTarget iff != null when used in an execution join point: thisJoinPoint.getThis == thisJoinPoint.getTarget ================================================================================ PART 2.0: 10pts The problem with the code "as-is" is that one global HashMap of instance variables is created, i.e. all instance variables are stored in the HashMap of the same Checker object, so that there is no way to distinguish which instance variables are "immediate part objects". public aspect Checker extends Supplier perthis(Any.ConstructorExecution()){ //this is the missing line The missing line includes the "perthis(Pointcut)" aspect designator keyword. The "perthis(Pointcut)" keyword designates that this aspect will be created for every instance of the Pointcut described in the parenteses. When it is included in the program, it causes a Checker aspect to be instantiated and directly related to every object that is the executing object (i.e., "this") at any of the join points picked out by the "Any.ConstructorExecution()" join point. This means that in our program a Checker aspect is instantiated and directly related to all objects created in the scope of the user defined code. Each Checker aspect's pointcuts will only capture join points in the code of the object it is related to. Therefore this solves the problem with the "as-is" code by effectively creating a HashMap of instance variables for each newly created object. NOTE: There were many other variations that worked. However, all other working choices have the potential to capture too many join points in the application, i.e. more than one join point per object that "this" applies to in the pointcut. These solutions also worked only because AspectJ ensures that only one Aspect of a specific type is instantiated per object when using the "perthis" and "pertarget" designations.