Midterm COM 3205 Fundamentals of Software Engineering Open book and open notes October 31, 2002 Karl Lieberherr ======================================================================= Question 1: 25 points Question 2: 10 points Question 3: 30 points Question 4 54 points ========================== 119 total Put answers to question 4 on the answer form. Question 1: ====================================================== 25 points Consider the object tree a_in representing an object of class A in the Java program below. We want to traverse this object in depth-first order as shown in the implementation below. Consider the method t. It's code is spread across several classes. 1. Write an aspect that localizes that code. 10 points 2. Write an aspect that prints the following for every D-object d1 that is found during some traversal t: a) d1.toString() b) x2.toString() for the object x2 visited immediately before the D-object d1 is visited. Your program should be generic and work for any application that has a method t() that is called on D-objects. 15 points The program: class A { B b; C c; A(B b, C c){ this.b = b; this.c = c;} void t() { b.t(); c.t(); } } class B { D d; E e; B(D d, E e) {this.d = d; this.e = e;} void t() { d.t(); e.t(); } } class C { void t() {} } class D extends C { void t() { System.out.println(" in D-object "); } } class E extends C { void t() { System.out.println(" in E-object "); } } class Main { static public void main(String args[]) { A a_in = new A(new B(new D(), new E()), new D()); a_in.t(); } } Question 2: ====================================================== 10 points You forgot to add a line at the end of your Main.main function in file Main.java. Write an aspect that adds the line to your program without modifying Main.java. The line you want to add is the print statement: System.out.println(" done "); Question 3: ====================================================== 30 points Consider a company who has so far used Java as implementation language with a version of the Rational Unified Process (RUP) where you start with use cases, implement them and use them for testing. Suppose you are a consultant to the company and they ask you how to move to AspectJ and how to adapt their process. How do you respond? Some questions you should answer: How can they use AspectJ for Java software development to become familiar with AspectJ? How? Give an example. How can they implement use cases with AspectJ? What is the difference to implementing them in Java? Question 4 ===================================================== 18 UNKNOWNs: 54 points Consider the AspectJ program shown at the end of this question for checking the object form of the Law of Demeter. It is incomplete in that it deals only with immediate parts. Now we want to extend it to deal with the other rules. Therefore we add the following aspect TargetBinStack which serves as superaspect of several subaspects: package lawOfDemeter; import org.aspectj.lang.*; import java.util.*; /** * The BinStack aspect is part of a Law of Demeter Checker - a set of * classes and aspects that verify a given program does not violate * the Law of Demeter. This aspect addresses the concern of storing a stack of * bins that contain the targets being examined * * @author David H. Lorenz * @author Modifications made by Paul Freeman */ abstract aspect TargetBinStack extends Supplier { /** * Used in debugging print statements. * @return Returns a string describing what this bin contains. */ abstract String contains(); /** * This is the pointcut used by the before and after advice to stack the * target bins. */ abstract pointcut EventToStack(); /** * This is the pointcut used by the around advice to gather the objects. */ abstract pointcut EventToGather(); /** * The method that fires to the implementing aspect before the EventToGather * proceeds, signalling gathering within the implementing aspect to take place. */ abstract void gatherBefore(JoinPoint thisJoinPoint); /** * The method that fires to the implementing aspect before the EventToGather * proceeds, signalling gathering within the implementing aspect to take place. */ abstract void gatherAfter(JoinPoint thisJoinPoint, Object returnValue); /** * Advice that maintains a stack of targets. */ Stack targetStack = new Stack(); Object around(): // keep track of arguments EventToStack(){ // before a method executes, we store the current stack of targets targetStack.push(targets); // and initialize a new stack of targets targets = new HashMap(); Object returnVal = proceed(); // after the method executes, we restore the previous stack of targets targets = (HashMap)targetStack.pop(); return returnVal; } /** * Fires the gather method to the implementing aspect. The advice must occur * here in the implementing class so that it can be ordered correctly with the * gathering advice. If the gathering advice was implemented in the subclass, * it would fire before the super classes advice. The dominates keyword will not * override this. */ Object around(): EventToGather(){ gatherBefore(thisJoinPoint); Object returnValue = proceed(); gatherAfter(thisJoinPoint, returnValue); return returnValue; } /** * Prints the objects in the current bin. * Used for debugging. */ public void printTopBin(){ System.out.println("== TOP "+contains()+" =="); printHashMap(targets); System.out.println("======================"); } /** * Prints each bin in the stack. * Used for debugging. */ public void printAllBins(){ System.out.println("== ALL "+contains()+" =="); // targetStack contains one empty bin by default so start at size - 1 for(int i = targetStack.size() - 1; i >= 0; i--){ System.out.println("-- bin " + i + " --"); if(i == 0){printHashMap(targets);} else{printHashMap((HashMap)targetStack.elementAt(i - 1));} System.out.println("-------------------"); } System.out.println("======================"); } } // end of TargetBinStack ============================================================ Now we focus on the two abstract pointcuts and the two abstract methods in TargetBinStack: abstract pointcut EventToStack(); abstract pointcut EventToGather(); abstract void gatherBefore(JoinPoint thisJoinPoint); abstract void gatherAfter(JoinPoint thisJoinPoint, Object returnValue); Below are the concrete implementations in three subaspects to complete the implementation of the Law of Demeter checker: Find the UNKNOWNs. Note that NOTHING is also a valid answer for some UNKNOWNs and UNKNOWNs may contain spaces and special characters. public aspect ArgumentBin extends TargetBinStack { pointcut EventToStack(): Any.UNKNOWN1(); pointcut EventToGather(): Any.UNKNOWN2(); void gatherBefore(JoinPoint thisJoinPoint){ // store the arguments of the method addAll(UNKNOWN3(), UNKNOWN4 + at(thisJoinPoint)); // arguments include this/self - store the object currently executing add(UNKNOWN5(), UNKNOWN6 + at(thisJoinPoint)); } void gatherAfter(JoinPoint thisJoinPoint, Object returnValue){ UNKNOWN7 } ... } public aspect LocallyConstructedBin extends TargetBinStack { pointcut EventToStack(): Any.UNKNOWN8(); pointcut EventToGather(): Any.UNKNOWN9(); void gatherBefore(JoinPoint thisJoinPoint){ UNKNOWN10 } void gatherAfter(JoinPoint thisJoinPoint, Object newLocalObj){ UNKNOWN11 add(UNKNOWN12, UNKNOWN13 + at(thisJoinPoint)); } ... } public aspect ReturnValueBin extends TargetBinStack { pointcut EventToStack(): Any.UNKNOWN14(); pointcut EventToGather(): Any.UNKNOWN15(); void gatherBefore(JoinPoint thisJoinPoint){ UNKNOWN16 } void gatherAfter(JoinPoint thisJoinPoint, Object returnVal){ UNKNOWN17 } } ... } To make the program work there is a small update to the Checker aspect needed. Put your answer in UNKNOWN18. ========= Program we start out with: Incomplete LoD checker ========= for object form package lawOfDemeter; import java.util.*; /** * The Any class is part of a Law of Demeter Checker - a set of * classes and aspects that verify a given program does not violate * the Law of Demeter. This class contains all of the pointcuts that * need to be examined in an application to verify it meets the law of demeter. * * @author David H. Lorenz * @author Modifications made by Paul Freeman */ abstract class Any { private pointcut scope(): !within(lawOfDemeter..*) && (!cflow(execution(* lawOfDemeter..*(..))) || cflow(execution(* lawOfDemeter.Tests.*(..)))); // capturing method calls. pointcut ConstructorCall(): scope() && call(*.new(..)); pointcut MethodCall(): scope() && call(* *(..)); // capturing method executions pointcut MainMethodExecution(): scope() && execution(void *.main(String[])); pointcut MethodExecution(): scope() && execution(* *(..)); pointcut ConstructorExecution(): scope() && execution(*.new (..)); pointcut Execution(): ConstructorExecution() || MethodExecution(); // capturing assignments to data members pointcut Set(): scope() && set(* *.*) ; // capturing accesses of data members pointcut Get(): scope() && get(* *.*); //Modify the pointcuts below to exclude join points from being checked by the application // denotes classes to avoid checking pointcut ToAvoid(): !Stable() && !(call(static * testcase..*(..)) || get(static testcase..* *)); // NOTE: With the following pointcuts, to nullify the pointcut, i.e. // have it pick out no join points, use: // Nothing() // as the sole designation // denotes all globally preferred static members // NOTE: scope() must always remain and be &&'ed with any further // pointcuts added. Multiple pointcuts added should be placed in parenteses // and separated with ||, i.e. scope() && (pointcut1() || pointcut2()) pointcut GlobalPreferred(): scope() && Nothing(); // denotes stable classes calls to which should not be checked; // multiple pointcuts should be separated with || pointcut Stable(): AllJavaPackageAccesses() ; // // what follows is a series of useful predefined pointcuts // // denotes the opposite of all classes // !within(*) can be read as not within any package. // It can be used to represent the empty set private pointcut Nothing(): !within(*); //denotes all statically accessed immediate parts and methods private pointcut AllStaticAccesses(): scope() && call(static * *.*(..)) || get(static * *); private pointcut AllJavaPackageAccesses(): scope() && call(* java..*(..)) || get(java..* *); } package lawOfDemeter; import java.util.*; /** * The Checker aspect is part of a Law of Demeter Checker - a set of * classes and aspects that verify a given program does not violate * the Law of Demeter. This aspect verifies that method calls do not * violate the Law of Demeter. * * @author David H. Lorenz * @author Modifications made by Paul Freeman */ public aspect Checker { // statistcs variables private long totalChecks = 0; private long numViolations = 0; private Vector validChecks = new Vector(); // position 0 = total valid checks // position i = total valid checks where // i = num of matches perCheck /** * Advice that does the checking. */ before(): (Any.MethodCall() || Any.Get()) && Any.ToAvoid(){ Object thisObj = thisJoinPoint.getThis(); Object target = thisJoinPoint.getTarget(); Vector suppliers = new Vector(); int binCount = 0; totalChecks++; // get the global "Bin" objects // suppliers.add( ArgumentBin.aspectOf() ); // suppliers.add( LocallyConstructedBin.aspectOf() ); // suppliers.add( ReturnValueBin.aspectOf() ); // suppliers.add( GlobalPreferredBin.aspectOf() ); // get the "Bin" objects associated with the currently executing object (this) try{ // this needs to be "tried" as there might NOT be an aspect associated with this obj suppliers.add( ImmediatePartBin.aspectOf(thisObj) ); }catch(org.aspectj.lang.NoAspectBoundException e){ } //check if target is in any of the bins boolean isInBin = false; Iterator supIt = suppliers.iterator(); while(supIt.hasNext()){ Supplier thisBin = (Supplier)supIt.next(); if(thisBin.contains(target)){ isInBin = true; binCount++; } } if (!isInBin){ // if the target is not a preferred supplier -> Violation numViolations++; System.out.println("!! LoD violation: " + thisJoinPoint + JPUtil.at(thisJoinPoint)); }else{ addToStats(binCount); } } // Keeps track of the valid count statistics private void addToStats(int binCount){ //initialize count being increased if necessary try{ if(validChecks.elementAt(0) == null); }catch(java.lang.ArrayIndexOutOfBoundsException e){ validChecks.setSize(1); validChecks.setElementAt(new Long(0), 0); // total count } try{ if(validChecks.elementAt(binCount) == null); }catch(java.lang.ArrayIndexOutOfBoundsException e){ validChecks.setSize(binCount + 1); validChecks.setElementAt(new Long(0), binCount); } // increase counts Long val; val = new Long(((Long)validChecks.elementAt(0)).longValue() + 1); validChecks.setElementAt(val, 0); val = new Long(((Long)validChecks.elementAt(binCount)).longValue() + 1); validChecks.setElementAt(val, binCount); } // prints statistics when program is done executing after(): Any.MainMethodExecution(){ long totalValid = 0; try{ totalValid = ((Long)validChecks.elementAt(0)).longValue(); }catch(java.lang.ArrayIndexOutOfBoundsException e){ } System.out.println("\nStatistics:"); System.out.println("Type \t\tCount\tValid%\tTotal%"); System.out.println("Total Checks\t\t" + totalChecks); System.out.println("Violations \t\t" + numViolations + "\t"+ fix((double)numViolations/(double)totalValid * 100, 2) + "\t"+ fix((double)numViolations/(double)totalChecks * 100, 2)); for(int i = 0; i < validChecks.size(); i++){ String type = ""; if(i==0){type = "Valid Checks";} else if(i==1){type = "As "+i+" Supplier";} else{type = "As "+i+" Suppliers";} long thisCount = ((Long)validChecks.elementAt(i)).longValue(); System.out.println(type + "\t\t" + thisCount + "\t"+ fix((double)thisCount/(double)totalValid * 100, 2) + "\t"+ fix((double)thisCount/(double)totalChecks * 100, 2)); } } // fixes a double to a certain number of decimal places private double fix(double aDouble, int numDec){ try{ String d = new String(new Double(aDouble).toString()); int dotPos = d.indexOf('.'); int len = d.length(); int lastPos = dotPos + numDec + 1; if(lastPos > len){lastPos = len;} return new Double(d.substring(0, lastPos)).doubleValue(); }catch(java.lang.NumberFormatException e){ return 0; } } } package lawOfDemeter; import java.util.*; /** * The ImmediatePartBin aspect is part of a Law of Demeter Checker - a set of * classes and aspects that verify a given program does not violate * the Law of Demeter. This aspect stores the immediate parts, or * instance variables, of objects as they are set for later * examination by the Checker aspect. * * @author David H. Lorenz * @author Modifications made by Paul Freeman */ public aspect ImmediatePartBin extends Supplier perthis(Any.ConstructorExecution()){ /** * Prints the objects currently in the bin. * Used for debugging. */ public void printBin(){ System.out.println("-- immediate part objects --"); printHashMap(targets); System.out.println("----------------------------"); } /** * Advice that stores the immediate part objects, or instance variables, of * the object currently executing. */ before(): // direct parts Any.Set() { // There is only one argument to a set join point. // Becomes a preferred supplier and is therefore added to targets. addAll(thisJoinPoint.getArgs(), direct+at(thisJoinPoint)); // System.out.println("Adding--" + thisJoinPoint + at(thisJoinPoint)); } } package lawOfDemeter; public class JPUtil { protected static String at(org.aspectj.lang.JoinPoint jp) { return " at " + jp.getSourceLocation().getFileName() + ":" + jp.getSourceLocation().getLine() + ":" + jp.getSourceLocation().getColumn() + " this " + jp.getThis() + " target " + jp.getTarget(); } }package lawOfDemeter; import java.util.*; /** * The Supplier class is a super class for the bin aspects. * It provides common functionality around a HashMap (targets) * that keeps track of the preferred supplier objects stored in the * various bins. * * @author David H. Lorenz * @author Modifications made by Paul Freeman */ abstract class Supplier { boolean contains(Object target) { if (targets.containsKey(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