This is an experimental feature, to use it, check out jboss-head from our cvs, and rebuild the aop and aspects projects.
In Design by Contract there are three kinds of expressions
You specify pre-, postconditions, and invariants by simply annotating the method or class. We'll start off by going through a few very simple cases as an introduction to the notation used by the DbC aspect.
/* * @@org.jboss.aspects.dbc.Dbc */ public class PreCondExample { /** * @@org.jboss.aspects.dbc.PreCond ({"$0 > i"}) */ public void squareRoot(int i) { .. } }The value $0 means the first of all the parameters (i.e. 'i').
/* * @@org.jboss.aspects.dbc.Dbc */ public class PostCondExample { /** * @@org.jboss.aspects.dbc.PostCond ({"$rtn = $0 * $1"}) */ public int multiply(int i, int j) { .. } }
The value $rtn is a reserved word, and means the value returned by the method. Again, the value $0 means the first of all the parameters (i.e. 'i'), and $1 means the second of all the parameters (i.e. 'j')
You can also refer to the target object in your conditions, by using the $tgt keyword (note that here we have more than one precondition):
/* * @@org.jboss.aspects.dbc.Dbc */ public class PostCondExample2 { private String name; /** * @@org.jboss.aspects.dbc.PreCond ({"$0 != null", "$0.length() > 0"}) * @@org.jboss.aspects.dbc.PostCond ({"$tgt.getName().equals($0)"}) */ public int setName(String name) { this.name = name; } public int getName() { return name; } }
So before the method setName() executes, the name parameter must not be not null or an empty string, and after setName() checks that the name member on the object is the same as what we set it to.
You can also access private variables from the assertions, the getName() method is the same as in the previous example:
/** * @@org.jboss.aspects.dbc.PostCond ({"$tgt.name.equals($rtn)"}) */ public int getName() { return name; }
/* * @@org.jboss.aspects.dbc.Dbc * @@org.jboss.aspects.dbc.Invariant ({"$tgt.myString != null || $tgt.myString.length() > 0"}) */ public class InvariantExample { String myString; public void setMyString(String s) { myString = s; } }
So before invoking any of the public methods on this object, the myString member must either be null or it must not be empty.
client makes call --- DbC aspect --- check invariants for target object (if invocation is not a constructor) check the preconditions for the method/constructor we want to invoke invoke the target method/constructor check the postconditions for the invoked method/constructor check the invariants for the target object --- DbC aspect - end --- client regains control
/* * @@org.jboss.aspects.dbc.Dbc */ public interface RootInterface { /** * @@org.jboss.aspects.dbc.PreCond ({"$0 > 0"}) */ public void someMethod(int i); } public class MyImpl implements RootInterface { public void someMethod(int i) { } }
MyImpl.someMethod() will now have the precondition "$0 > 0" bound to it.
The above will hopefully be a bit clearer when we show how to include the aspect:
<?xml version="1.0" encoding="UTF-8"?> <aop> <aspect class="org.jboss.aspects.dbc.DesignByContractAspect" scope="PER_JOINPOINT"> <attribute name="verbose">false</attribute> </aspect> <bind pointcut="execution(* $instanceof{@org.jboss.aspects.dbc.Dbc}->*(..)) OR execution($instanceof{@org.jboss.aspects.dbc.Dbc}->new(..))"> <advice aspect="org.jboss.aspects.dbc.DesignByContractAspect" name="invoke"/> </bind> </aop>
For the RootInterface/MyImpl hierarchy just mentioned, now if we define another class:
public class MySubImpl implements RootInterface { /** * @@org.jboss.aspects.dbc.PreCond ({"$0 % 2 == 0"}) */ public void someMethod(int i) { } }
When invoking MySubImpl.someMethod(), the condition higher up in the hierarchy must still hold, as well as the new one. So MySubImpl.someMethod() has two preconditions: "$0 > 0" (The parameter must be positive) AND "$0 % 2 == 0" (The parameter must be an even number). The same mechanism applies to invariants and postconditions as well.
Consider the class
/* * @@org.jboss.aspects.dbc.Dbc * @@org.jboss.aspects.dbc.Invariant ({"$tgt.myString != null || $tgt.myString.length() > 0"}) */ public class InvariantExample { String myString; static String myStaticString; public void setMyString(String s) { myString = s; } public static void setMyStaticString(String s) { myStaticString = s; } }
If we call setMyStaticString(), which is a static method, the calls to the defined invariant would fail, since we have no access to any non-static methods or member variables. To avoid this, the framework skips evaluation of the invariants when static calls are made. This is not really a problem, since the non-static member values should not have changed as a result of invoking a static method anyway. In order to be able to check static class members you can use the @@org.jboss.aspects.dbc.StaticInvariant annotation.
/* * @@org.jboss.aspects.dbc.Dbc * @@org.jboss.aspects.dbc.Invariant ({"$tgt.myString != null || $tgt.myString.length() > 0"}) * @@org.jboss.aspects.dbc.StaticInvariant ({"$tgt.myStaticString != null || $tgt.myStaticString.length() > 0"}) */ public class InvariantExample { String myString; static String myStaticString; public void setMyString(String s) { myString = s; } public static void setMyStaticString(String s) { myStaticString = s; } }Now we have the same type of checks for the static member as we do for the non-static one. StaticInvariants can NOT access any non-static methods or members, but they will be evaluated when a public method is called regardless of if the method is static or not.
public class RecursiveChecks { /** * @@org.jboss.aspects.dbc.PreCond ({"$tgt.getAddress() != null"}) */ public String getName() { } /** * @@org.jboss.aspects.dbc.PreCond ({"$tgt.getName() != null"}) */ public String getAddress() { } }
There is an algorithm in place to stop infinite recursion of checks from taking place.
<boolean expression A> implies <boolean expression B>
Here's an example:
package test.dbc.office; /** * @@org.jboss.aspects.dbc.Dbc */ public class Developer { ... Computer computer; /** *@@org.jboss.aspects.dbc.PostCond ({"$rtn != null implies ($rtn.getDeveloper() == null) || ($rtn.getDeveloper() == $tgt)"}) */ public Computer getComputer() { return computer; } } /** * @@org.jboss.aspects.dbc.Dbc */ public class Computer { ... Developer developer; /** *@@org.jboss.aspects.dbc.PostCond ({"$rtn != null implies ($rtn.getComputer() == null) ||($rtn.getComputer() == $tgt)"}) */ public Developer getDeveloper() { return developer; } }
forall <loop variable> in <collection or array> | <condition>
Here's an example:
package test.dbc.office; /** * @@org.jboss.aspects.dbc.Dbc * @@org.jboss.aspects.dbc.Invariant ({"forall test.dbc.office.Computer c in $tgt.computers | c != null"}) */ public class OfficeManager { ArrayList computers = new ArrayList(); ... }
The typing of the loop variable is optional:
package test.dbc.office; /** * @@org.jboss.aspects.dbc.Dbc * @@org.jboss.aspects.dbc.Invariant ({"forall d in $tgt.developers | d != null"}) */ public class OfficeManager { ArrayList developers = new ArrayList(); ... }
The examples declare that the computers and developer collections cannot contain null values.
exists <loop variable> in <collection or array> | <condition>
Here's an example:
package test.dbc.office; /** * @@org.jboss.aspects.dbc.Dbc */ public class OfficeManager { ArrayList computers = new ArrayList(); ArrayList developers = new ArrayList(); ... /** * @@org.jboss.aspects.dbc.PreCond ({"exists test.dbc.office.Computer c in $tgt.computers | c.getDeveloper() == null && c == $0"}) */ public void assignComputer(Computer computer, Developer developer) { ... } }
The precondition states that there has to be a Computer that has not been assigned to a developer, and that the "free" Computer has to be the same as the one passed in as a parameter to the method.
Here's an example:
package test.dbc.office; /** * @@org.jboss.aspects.dbc.Dbc */ public class OfficeManager { ArrayList computers = new ArrayList(); ArrayList developers = new ArrayList(); ... /** * @@org.jboss.aspects.dbc.PostCond ({"forall d in $tgt.developers | exists c in $tgt.computers | (c == d.getComputer() && d == c.getDeveloper())"}) */ public void assignComputer(Computer computer, Developer developer) { ... } }
/** * @@org.jboss.aspects.dbc.Dbc */ public class Sorter { /** * Returns the original array with all the elements sorted incrementally * @@org.jboss.aspects.dbc.PostCond ({"java: for (int i = 0 ; i < $rtn.length ; i++){if (i > 0){if ($rtn[i] < $rtn[i - 1])return false;}}return true;"}) */ public static int[] sort(int[] unsorted) { ... } }
If we indent the java expression a bit nicer, it reads:
for (int i = 0 ; i < $rtn.length ; i++) { if (i > 0) { if ($rtn[i] < $rtn[i - 1]) return false; } } return true;
--- Go back to JBossAOP