ogp-notes

How to properly document multi-object abstractions

These instructions complement the instructions on How to properly document single-object abstractions.

Fields documentation

If an @invar clause for an object O mentions a field of an object O’, then O’ must be a representation object of O or a peer object of O.

An @invar clause must be well-defined (i.e. must not crash or loop forever) for arbitrary (concrete) states of the objects involved, with the following exceptions:

Advanced topics

You need not read or apply this section if you are not going for a top score.

To achieve a complete specification of the behavior of a method or constructor, the @peerObject and @peerObjects clauses should be used to define an object’s peer group, and the @mutates, @mutates_properties, and @inspects clauses should be used to indicate which pre-existing peer groups are mutated and inspected by the method or constructor, and the @creates clause should be used to indicate which peer groups that are visible to the client after the method or constructor finishes were created by the method or constructor.

For more information about peer groups, see the course notes on Entity-relationship abstractions.

Mutates clauses

A method must not mutate any pre-existing peer groups not mentioned in its @mutates clause and not used to represent the state of any peer groups mentioned in its @mutates clause. That is, an execution of a method may mutate an object O if and only if either O was newly created during the method execution (i.e. it did not exist when the method execution started), or a member of O’s peer group is mentioned in the method’s @mutates clause, or O is a member of the peer group of a representation object of a member of the peer group of an object mentioned in the method’s @mutates clause, or O is a member of the peer group of a representation object of a member of the peer group of a representation object of a member of the peer group of an object mentioned in the method’s @mutates clause, and so forth.

@mutates_properties clauses

If a peer group is mentioned in a @mutates clause, then the new state of the peer group must be specified using postconditions. In general, the constructor or method’s postconditions must (explicitly or by implication) specify the new return value of each getter of each member of the peer group. If most objects’ getters’ return values remain unchanged, and for the objects whose getters’ return values do not all remain unchanged, most getters’ return values do remain unchanged, one can use a @mutates_properties clause.

The clause @mutates_properties | O1.M1(), O2.M2() is equivalent to @mutates | O1, O2 plus a postcondition that states that for each object O in the union of the peer group of O1 and O2, and for each basic inspector M of O, either (O, M) is in the set {(O1, M1), (O2, M2)} or Objects.equals(O.M(), old(O.M())). A basic inspector is a getter that does not have a postcondition of the form result == E or result.equals(E) or Objects.equals(result, E) for some expression E.

Inspects clauses

Similarly, a method must not inspect the state of any pre-existing mutable objects that are not in the peer group of any object mentioned in its @inspects or @mutates clause and not used to represent the state of any objects mentioned in its @inspects or @mutates clause.

Defaults

If no @mutates, @mutates_properties, or @inspects clause is specified for a given method or constructor, the default is that it may inspect and mutate any object that is not an instance of an immutable class. Exception: if an instance method’s name starts with get or is, the default is that it may mutate no object and that it may inspect this.

Creates clauses

By specifying an object in a @creates clause, you indicate that every member of the object’s peer group was created during the execution of the method, and furthermore, that the peer group is disjoint from that of any direct or indirect representation object of any peer group mentioned in any of the method’s @inspects or @mutates clauses.

Note: this meaning of @creates clauses does not apply if the specified expression evaluates to null or to an immutable object. In these cases, the clause is simply ignored.

Evaluation

The expressions that appear in @mutates, @mutates_properties, and @inspects clauses are evaluated before the execution of the method or constructor. For example, the clause @mutates_properties | this.getTeam(), this.getTeam().getMembers() in the documentation of method leaveTeam of class ProjectCourseStudent means that the method can mutate the getTeam() property of this and the getMembers() property of the object identified by the old value of this.getTeam().

The objects listed in @mutates and @inspects clauses are the pre-existing objects that the method or constructor can mutate or inspect. It follows that it does not make sense to mention this in the @mutates clause of a constructor, since this is not a pre-existing object. Similarly, the properties listed in @mutates_properties clauses are the properties of pre-existing objects that the method or constructor can mutate. It follows that it does not make sense to mention properties of this in the @mutates_properties clause of a constructor.

Specifying collections of objects

One can specify a collection of objects in a @mutates, @mutates_properties, @inspects, or @creates clause using the ...collection syntax:

/**
 * @inspects | lists, strings
 * @mutates | ...lists
 */
static void allAddAll(StringList[] lists, String[] strings) {
    for (StringList list : lists)
        list.addAll(strings);
}

/**
 * @inspects | lists, ...lists
 */
static boolean anyIsEmpty(StringList[] lists) {
    for (StringList list : lists)
        if (list.getElements().length == 0)
            return true;
    return false;
}

class Rectangle {

    private int width;
    private int height;

    public int getWidth() { return width; }

    public int getHeight() { return height; }

    /** @post | result == getWidth() * getHeight() */
    public int getArea() { return width * height; }

    /**
     * @mutates_properties | getWidth()
     * @post | getWidth() == newWidth
     */
    public void setWidth(int newWidth) { width = newWidth; }

    /**
     * @inspects | rectangles
     * @mutates_properties | (...rectangles).getWidth()
     * @post | Arrays.stream(rectangles).allMatch(r -> r.getWidth() == newWidth)
     */
    public static void allSetWidth(Rectangle[] rectangles, int newWidth) {
        for (Rectangle rectangle : rectangles)
            rectangle.setWidth(newWidth);
    }

}