|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 3 of 4
/**
* Append an element to a collection.
*
* @post c.size() = c@pre.size() + 1
* @post c.contains(o)
*/
public void append(Collection c, Object o) { ... }
In the code above, the first postcondition specifies that the size of the collection must grow by 1 when we append an element.
The expression c@pre refers to the collection c before execution of the append method.
With iContract, you can specify invariants in the header comment of a class definition:
/**
* A PositiveInteger is an Integer that is guaranteed to be positive.
*
* @inv intValue() > 0
*/
class PositiveInteger extends Integer
{
...
}
In this example, the invariant guarantees that the PositiveInteger's value is always greater than or equal to zero. That assertion is checked before and after execution of any method of that
class.
Although the assertion expressions in iContract are valid Java expressions, they are modeled after a subset of the Object Constraints Language (OCL). OCL is one of the standards maintained and coordinated by the Object Management Group, or OMG. (OMG takes care of CORBA and related stuff, in case you miss the connection.) OCL was intended to specify constraints within object modeling tools that support the Unified Modeling Language (UML), another standard guarded by OMG.
Because the iContract expressions language is modeled after OCL, it provides some advanced logical operators beyond Java's own logic operators.
iContract supports forall and exists quantifiers. The forall quantifier specifies that a condition should hold true for every element in a collection:
/* * @invariant forall IEmployee e in getEmployees() | * getRooms().contains(e.getOffice()) */
The above invariant specifies that every employee returned by getEmployees() has an office in the collection of rooms returned by getRooms(). Except for the forall keyword, the syntax is the same as that of an exists expression.
Here is an example using exists:
/** * @post exists IRoom r in getRooms() | r.isAvailable() */
That postcondition specifies that after the associated method executes, the collection returned by getRooms() will contain at least one available room. The exists proceeds the Java type of the collection element -- IRoom in the example. r is a variable that refers to any element in the collection. The in keyword is followed by an expression that returns a collection (Enumeration, Array, or Collection). That expression is followed by a vertical bar, followed by a condition involving the element variable, r in the example. Employ the exists quantifier when a condition must hold true for at least one element in a collection.
Both forall and exists can be applied to different kinds of Java collections. They support Enumerations, Arrays, and Collections.
iContract provides the implies operator to specify constraints of the form, "If A holds, then B must hold as well." We say, "A implies B." Example:
/** * @invariant getRooms().isEmpty() implies getEmployees().isEmpty() // no rooms, no employees */
That invariant expresses that when the getRooms() collection is empty, the getEmployees() collection must be empty as well. Note that it does not specify that when getEmployees() is empty, getRooms() must be empty as well.
You can also combine the logical operators just introduced to form complex assertions. Example:
/** * @invariant forall IEmployee e1 in getEmployees() | * forall IEmployee e2 in getEmployees() | * (e1 != e2) implies e1.getOffice() != e2.getOffice() // a single office per employee */
iContract propagates constraints along inheritance and interface implementation relationships between classes and interfaces.
Suppose class B extends class A. Class A defines a set of invariants, preconditions, and postconditions. In that case, the invariants and preconditions of class A apply to class B as well, and methods in class B must satisfy the same postconditions that class A satisfies. You may add more restrictive assertions to class B.
The aforementioned mechanism works for interfaces and implementations as well. Suppose A and B are interfaces and class C implements both. In that case, C is subject to invariants, preconditions, and postconditions of both interfaces, A and B, as well as those defined directly in class C.
iContract will improve the quality of your software by allowing you to catch many possible bugs early on. But you can also shoot yourself in the foot (that is, introduce new bugs) using iContract. That can happen when you invoke functions in your iContract assertions that engender side effects that alter your system's state. That leads to unpredictive behavior because the system will behave differently once you compile your code without iContract instrumentation.
Let us take a look at a complete example. I have defined the Stack interface, which defines the familiar operations of my favorite data structure:
/**
* @inv !isEmpty() implies top() != null // no null objects are allowed
*/
public interface Stack
{
/**
* @pre o != null
* @post !isEmpty()
* @post top() == o
*/
void push(Object o);
/**
* @pre !isEmpty()
* @post @return == top()@pre
*/
Object pop();
/**
* @pre !isEmpty()
*/
Object top();
boolean isEmpty();
}
We provide a simple implementation of the interface:
import java.util.*;
/**
* @inv isEmpty() implies elements.size() == 0
*/
public class StackImpl implements Stack
{
private final LinkedList elements = new LinkedList();
public void push(Object o)
{
elements.add(o);
}
public Object pop()
{
final Object popped = top();
elements.removeLast();
return popped;
}
public Object top()
{
return elements.getLast();
}
public boolean isEmpty()
{
return elements.size() == 0;
}
}
As you can see, the Stack implementation does not contain any iContract assertions. Rather, all assertions are made in the interface, meaning that
the component contract of the Stack is defined in the interface in its entirety. Just by looking at the Stack interface and its assertions, the Stack's behavior is fully specified.
Now we add a small test program to see iContract in action:
public class StackTest
{
public static void main(String[] args)
{
final Stack s = new StackImpl();
s.push("one");
s.pop();
s.push("two");
s.push("three");
s.pop();
s.pop();
s.pop(); // causes an assertion to fail
}
}
Next, we run iContract to build the stack example: