-------------------------------------------------------------------------- Software Engineering Fall 2002 COM 3205 --------------------------------------------------------------------------- Assignment 2 Due date: Oct. 10, 2002 -------------------------------------------------------------------------- This assignment is stored in file $SE/hw/2 in file hw2-SE.txt -------------------------------------------------------------------------- Topics: Implementing a Crosscutting Style Rule Tracer Bullets: Tip 15: Use Tracer Bullets to Find the Target Section 10 in TPP Code That's Easy to Test: Tip 48: Design to Test Section 34 in TPP Tip 35: Finish What You Start. Encapsulate increase/decrease in around method. In hw 1 you wrote a requirement document for checking the Law of Demeter. In this hw we write a tool using AspectJ that checks for an "approximation" of the Law of Demeter. We use this tool as a tracer bullet to get an idea how a complete Law of Demeter checker would look like. AOP (AspectJ) concepts you need to learn: aspect join point class JoinPoint, thisJoinPoint pointcuts this, target, execution, within pointcut composition: !, && around advice with proceed PART 1 ==================================================== The tool has the following requirement: Consider a Java program P while it is executing. For each method call which is a local method call, i.e., it is of the form this.foo(..), print the message: "local call". In addition, for each method execution in your source code, print the method execution join points that are currently active (the stack trace) and the list of arguments of the execution that is on top of the stack. Implement your tool dynamically, using AspectJ. Hint: Use a Singleton Aspect Solution: Write a singleton aspect that captures all method calls and checks whether the target object is the current object. In a first step, find the UNKNOWNs in the following program. Then add statements to record the stack trace. // from sol/start-for-students2 // author: Pengcheng Wu and Karl Lieberherr import org.aspectj.lang.*; import org.aspectj.lang.reflect.SourceLocation; import java.util.*; UNKNOWN1 LoDPointCuts { // count the nesting level of the method executions // not the calls! int nestingLevel = UNKNOWN2; // consider all method executions pointcut Execution(): execution(* *(..)) && !within(LoDPointCuts) && !within(JPUtil) && !cflow(call (* JPUtil.*(..))); // consider all method calls pointcut Call(): call(* *(..)) && !within(LoDPointCuts) && !within(JPUtil) && !cflow(call (* JPUtil.*(..))); // maintain the stack of join points Object around(): Execution() { if (!(thisJoinPoint.getThis() == thisJoinPoint.getTarget())) // an assertion System.out.println(" ============ should never happen "); nestingLevel++; System.out.println(nestingLevel + " nl " + JPUtil.toString(thisJoinPoint)); Object p = proceed(); nestingLevel--; return p; } // report the positive situations after(): Call() { Object thisObject = thisJoinPoint.UNKNOWN3(); Object targetObject = thisJoinPoint.UNKNOWN4(); if (UNKNOWN5) { // print message: "local call" System.out.println(" *** OK, local call"); } if (thisObject == null) { System.out.println(JPUtil.toString(thisJoinPoint)); System.out.println(" *** call in static method"); } if (UNKNOWN6 == null) { System.out.println(JPUtil.toString(thisJoinPoint)); System.out.println(" *** call in static method"); } } } class JPUtil { static String toString(JoinPoint jp) { SourceLocation sl = jp.UNKNOWN7(); String fname = sl.getFileName(); int line = sl.getLine(); int col = sl.getColumn(); Object [] argsArray = jp.getArgs(); System.out.println("arguments"); for(int i=0; i>> " + jp.getSignature().toString() + " in " + fname + " line " + line + "(" + col + ") "; // + " args " + argsArray; // + " toLongString " + jp.toLongString(); } } When you run your program with the UNKNOWNs filled in, it should produce for the input: class Foo { Foo f ; public String toString() {return new String();} public Foo(Foo f) { this.f=f; } public int add(int a, int b, int c) { return a+b+c; } public Foo() { super(); } static void bar() { } void test() { f = new Foo(); String s = f.toString(); } Foo getF() { return f; } Object compute() { return new Object(); } public void run(Object x) { add(1,2,3); x.toString(); f.test(); Foo.bar(); getF().getF().test(); new Foo().test(); this.compute(); this.compute().toString(); compute(). toString(); } public static void main(String[] args) { Foo aFoo = new Foo(new Foo(new Foo())); aFoo.run(new Foo()); aFoo.toString(); } } the following output: arguments argument [Ljava.lang.String;@17d257 end arguments 1 nl >>> void Foo.main(String[]) in Foo.java line 39(4) arguments argument end arguments 2 nl >>> void Foo.run(Object) in Foo.java line 26(4) arguments argument 1 argument 2 argument 3 end arguments 3 nl >>> int Foo.add(int, int, int) in Foo.java line 8(4) *** OK, local call arguments end arguments 3 nl >>> String Foo.toString() in Foo.java line 4(4) arguments end arguments 3 nl >>> void Foo.test() in Foo.java line 16(4) arguments end arguments 4 nl >>> String Foo.toString() in Foo.java line 4(4) arguments end arguments 3 nl >>> void Foo.bar() in Foo.java line 14(4) arguments end arguments >>> void Foo.bar() in Foo.java line 30(7) *** call in static method arguments end arguments 3 nl >>> Foo Foo.getF() in Foo.java line 20(4) *** OK, local call arguments end arguments 3 nl >>> Foo Foo.getF() in Foo.java line 20(4) arguments end arguments 3 nl >>> void Foo.test() in Foo.java line 16(4) arguments end arguments 4 nl >>> String Foo.toString() in Foo.java line 4(4) arguments end arguments 3 nl >>> void Foo.test() in Foo.java line 16(4) arguments end arguments 4 nl >>> String Foo.toString() in Foo.java line 4(4) arguments end arguments 3 nl >>> Object Foo.compute() in Foo.java line 23(4) *** OK, local call arguments end arguments 3 nl >>> Object Foo.compute() in Foo.java line 23(4) *** OK, local call arguments end arguments 3 nl >>> Object Foo.compute() in Foo.java line 23(4) *** OK, local call arguments argument end arguments >>> void Foo.run(Object) in Foo.java line 41(7) *** call in static method arguments end arguments 2 nl >>> String Foo.toString() in Foo.java line 4(4) arguments end arguments >>> String Foo.toString() in Foo.java line 42(7) *** call in static method Turn in the following answers: 1.1: The 7 UNKNOWNs. 1.2: The program printing the stack trace. Turn in your AspectJ program and your test program and the output produced. 1.3: The above program violates the DRY principle. Explain why. PART 2 ==================================================== We implement another approximation to the Law of Demeter using a different design. Consider the following program. I have deleted one line from the program which makes the program behave incorrectly. Your task is to find the line I deleted. Turn in the missing line and the line before it. // sol/part-fix-per-this package lawOfDemeter; import java.util.*; /** * An implementation of a part of the LoD checker. Only allows calls to * immediate part objects (data members of this). * Will create many violations for this simplified form. * * @author David H. Lorenz (author of correct version) */ abstract class Any { // we encapsulate all pointcuts we need in a class and // refer to them later as Any.* // to avoid capturing join points in the advice // of the LoD checker itself. within(someClass) // picks out all join points where the executing code is defined in someClass. // ! is complement. private pointcut scope(): !within(lawOfDemeter..*) ; // not in this package // AspectJ doc: http://aspectj.org/doc/dist/progguide/apas02.html // .. in an identifier any sequence of characters starting and ending with "." // * is the wildcard symbol. l..* matches e.g. l.p1.p2.C and also l.C // capturing method calls. // thisJoinPoint.getThis() and thisJoinPoint.getTarget() denote usually two // distinct objects: the currently executing object and the target object. pointcut MethodCall(): scope() && call(* *(..)); // identical: pointcut MethodExecution(): scope() && execution(* *.*(..)); // The defining type name, if not present, defaults to * // AspectJ doc: http://aspectj.org/doc/dist/progguide/apbs02.html // capturing method executions. // thisJoinPoint.getThis() and thisJoinPoint.getTarget() denote identical // objects: the target object. // pointcut call(Signature) // Picks out a method or constructor call join point // based on the static signature at the caller side. // pointcut execution(Signature) // Picks out a method or constructor execution join point // based on the static signature at the callee side. pointcut MethodExecution(): scope() && execution(* *(..)); // capturing assignments to data members // All set join points are treated as having one argument, // the value the field is being set to. // example of set pointcut: set(int T.x) pointcut Set(): scope() && set(* *.*) ; // execution(*.new (..)) is NOT a subset of execution(* *(..)) ?? pointcut ConstructorExecution(): scope() && execution(*.new (..)); pointcut Execution(): ConstructorExecution() || MethodExecution(); } // a super class for the aspects. Keeps track of a HashMap targets // that keeps track of the preferred supplier objects. abstract class Supplier { boolean contains(Object target) { if (targets.containsKey(target)) { System.out.println(targets.get(target)); return true; } return false; } void add(Object target,String i) { targets.put(target,i); } void addAll(Object[] targetlist,String i) { for (int k=0; k Violation if (!contains(target)) System.out.println(" !! LoD Violation !! "); } } To install AspectJ in your environment, see: http://www.ccs.neu.edu/home/lieber/com3205/f02/CCS_AspectJ_Usage.html