CSC 308 Lecture Notes Weeks 6 and 7
Introduction to Fully Formal Specification
I want you to do whatever it takes to build me software of the best possible quality, that has the smallest possible likelihood of failing.
Predicate Logic: | Relational: | |||
Operator | Description | Operator | Description | |
&& | logical and | == | primitive equality | |
|| | logical or | !- | primitive inequality | |
! | logical not | < | primitive less than | |
if (...) | logical implication | > | primitive greater than | |
iff | logical equivalence | <= | primitive less than or equal to | |
if (...) else | conditional choice | >= | primitive grtr than or equal to | |
forall | universal quantification | .equals | object equality | |
exists | existential quantification | .compareTo | object comparison | |
Logical Extensions: | Arithmetic: | |||
Operator | Description | Operator | Description | |
x' | value after execution | + | addition | |
return | return value of method | - | subtraction | |
* | multiplication | |||
/ | division | |||
Collections, Lists, Strings: | ||||
Operator | Description | |||
.size() | size of collection | |||
.contains(Object o) | collection membership | |||
.get(int i) | get ith list element | |||
.length(String s) | length of s | |||
other collection ops | see Collection docs | |||
other list ops | see List docs | |||
other string ops | see String docs |
Table 1: Spest Expression Operators.
p | q | p => q | p | q | p <==> q | |
0 | 0 | 1 | 0 | 0 | 1 | |
0 | 1 | 1 | 0 | 1 | 0 | |
1 | 0 | 0 | 1 | 0 | 0 | |
1 | 1 | 1 | 1 | 1 | 1 |
p | x | y | p ? x : y |
0 | x | y | y |
1 | x | y | x |
Figure 1: User database maintenance dialog.
Figure 2: Group database maintenance dialog.
import java.util.Collection; /** * UserDB is the repository of registered user information. */ abstract class UserDB { /** * The collection of user data records. */ Collection<UserRecord> data; /** * Add the given UserRecord to the given UserDB. The Id of the given user * record must not be the same as a user record already in the UserDB. * The Id component is required and must be eight characters or less. The * email address is required. The phone number is optional; if given, the * area code and number must be 3 and 7 digits respectively. */ abstract void add(UserRecord ur); /** * Find a user by unique id. */ abstract UserRecord findById(String id); /** * Find a user or users by real-world name. If more than one is found, * the output list is sorted by id. */ abstract Collection<UserRecord> findByName(String name); /** * Change the given old UserRecord to the given new record. The old and * new records must not be the same. The old record must already be in * the input db. The new record must meet the same conditions as for the * input to the AddUser operation. Typically the user runs the FindUser * operation prior to Change to locate an existing record to be changed. */ abstract void change(UserRecord old_ur, UserRecord new_ur); /** * Delete the given user record from the given UserDB. The given record * must already be in the input db. Typically the user runs the FindUser * operation prior to Delete to locate an existing record to delete. */ abstract void delete(UserRecord ur); } /** * A UserRecord is the information stored about a registered user of the * Calendar Tool. The Name component is the user`s real-world name. The * Id is the unique identifier by which the user is known to the Calendar * Tool. The EmailAddress is the electronic mail address used by the * Calendar Tool to contact the user when necessary. The PhoneNumber is * for information purposes; it is not used by the Calendar Tool for * contacting the user. */ abstract class UserRecord { String name; String id; String email; PhoneNumber phone; } abstract class PhoneNumber { int area; int number; }
Figure 3: UML diagrams for UserDB objects and operations.
class ACollection {with the effect of adding an element to the data collection.
Collection<AnElement> data;
void constructiveOp(AnElement);
}
class ACollection {with the effect of finding zero or more elements in a collection.
AnElement selectiveOp(UniqueElementSelector);
Collection<AnElement> selectiveOp(NonUniqueElementSelector);
}
class ACollection {with the effect of removing the OldElement and adding the NewElement.
void modifyingOp(AnElement oldElement, AnElement newElement);
}
abstract UserRecord find(String id); abstract Collection<UserRecord> find(String name);
import java.util.Collection; abstract class UserDB { Collection<UserRecord> data; /** * Add the given UserRecord ur to this.data. The UserId of the given user * record must not be the same as a user record already in this.data. The * UserId component is required and must be eight characters or less. The * email address is required. The phone number is optional; if given, the * area code and number must be 3 and 7 digits respectively. * <pre> pre: // // The id of the given user record must be unique and less than or // equal to 8 characters; the email address must be non-empty; the // phone area code and number must be 3 and 7 digits, respectively. // post: // // The given user record is in this.data. // * */ abstract void add(UserRecord ur); }
import java.util.Collection; abstract class UserDB { Collection<UserRecord> data; /** * Add the given UserRecord ur to this.data. The UserId of the given user * record must not be the same as a user record already in this.data. The * UserId component is required and must be eight characters or less. The * email address is required. The phone number is optional; if given, the * area code and number must be 3 and 7 digits respectively. * <pre> pre: // // The id of the given user record must be unique and less than or // equal to 8 characters; the email address must be non-empty; the // phone area code and number must be 3 and 7 digits, respectively. // // *** Coming soon *** ; post: // // The given user record is in this.data. // data'.contains(ur); * */ abstract void add(UserRecord ur); }
import java.util.Collection; abstract class UserDB { Collection<UserRecord> data; /* post: // // The given user record is in this.data. // data'.contains(ur) && // // All the other records in the output db are those from the input db, // and only those. // forall (UserRecord ur_other ; !ur_other.equals(ur) ; if (data.contains(ur_other)) data'.contains(ur_other) else !data'.contains(ur_other)); */ abstract void add(UserRecord ur); }
forall (T x ; constraint ; predicate)
import java.util.Collection; abstract class UserDB { Collection<UserRecord> data; /* <pre> // // A user record is in the output data if and only if it is the new // record to be added or it is in the input data. // post: forall ( UserRecord ur_other ; (data'.contains(ur_other)) <==> ur_other.equals(ur) || data.contains(ur_other)); */ abstract void add(UserRecord ur); }
import java.util.Collection; abstract class UserDB { Collection<UserRecord> data; /** * Add the given UserRecord ur to this.data. The UserId of the given user * record must not be the same as a user record already in this.data. The * UserId component is required and must be eight characters or less. The * email address is required. The phone number is optional; if given, the * area code and number must be 3 and 7 digits respectively. */ /*@ ensures // // The given user record is in this.data, per the semantics of // Collection.add. // data'.equals(data.add(ur)); @*/ abstract void add(UserRecord ur); }where add in this context is the java.util.Collection.add method.
import java.util.Collection; abstract class UserDB { Collection<UserRecord> data; /** * Find a user by unique id. * * <pre> post: // // If there is a record with the given id in the input db, then the // output record is equal to that record, otherwise the output record // is empty. * */ UserRecord findById(String id); }
import java.util.Collection; abstract class UserDB { Collection<UserRecord> data; /* * Find a user by unique id. * <pre> pre: // None yet. ; post: // // If there is a record with the given id in the input data, then the // output record is equal to that record, otherwise the output record // is null. // exists (UserRecord ur_found ; data.contains(ur_found) ; ur_found.id.equals(id) && ur_found.equals(return)) || !exists ( UserRecord ur_found ; data.contains(ur_found) ; ur_found.id.equals(id)) && return == null; * */ abstract UserRecord findById(String id); /* * Find a user or users by real-world name. If more than one is found, * list is sorted by id. * <pre> pre: // None yet. ; post: // // A record is in the output list if and only it is in the input UserDB // and the record name equals the name being searched for. // forall ( UserRecord ur ; return.contains(ur) iff data.contains(ur) && ur.name.equals(name)); * */ abstract Collection<UserRecord> findByName(String name); /** * Change the given old UserRecord to the given new record. The old and * new records must not be the same. The old record must already be in * the input db. The new record must meet the same conditions as for the * input to the AddUser operation. Typically the user runs the FindUser * operation prior to Change to locate an existing record to be changed. */ /* <pre> pre: // None yet. ; post: // // A user record is in the output data if and only if it is the new // record to be added or it is in the input data, and it is not the old // record. // forall ( UserRecord ur_other ; data'.contains(ur_other) iff ur_other.equals(new_ur) || (data.contains(ur_other) && !ur_other.equals(old_ur))); * */ abstract void change(UserRecord old_ur, UserRecord new_ur); /** * Delete the given user record from the given UserDB. The given record * must already be in the input db. Typically the user runs the FindUser * operation prior to Delete to locate an existing record to delete. * <pre> pre: // None yet. ; post: // // A user record is in the output data if and only if it is not the // existing record to be deleted and it is in the input data. // forall ( UserRecord ur_other ; data'.contains(ur_other) iff !ur_other.equals(ur) && data.contains(ur_other)); * */ abstract void delete(UserRecord ur); }
Form | Reading |
exists (T x ; predicate) | There exists x of type T such that predicate is true. |
exists (T x ; constraint ; predicate) | There exists x of type T, such that constraint is true, and then predicate is true. |
Table 2: Forms of existential quantification..
forall (T x ; p) <==> !exists (T x ; !p) and !forall (T x ; !p) <==> exists (T x ; p)
forall (UserRecord ur ; requirement-predicate)
exists (UserRecord ur ; requirement-predicate)
forall (UserRecord ur ; data.contains(ur) ; requirement-predicate)
exists (UserRecord ur ; data.contains(ur) ; requirement-predicate)
forall (int i ; i > 0 ; requirement-predicate)
exists (int i ; i > 0 ; requirement-predicate)
A UserDB shall not contain duplicate entries.
import java.util.Collection; abstract class UserDB { Collection<UserRecord> data; /** * Same comment as above ... . * <pre> pre: // // There is no user record in the input UserDB with the same id as the // record to be added. // !exists (UserRecord ur_input ; data.contains(ur_input) ; ur_input.id.equals(ur.id)); post: // Same postcondition as above ... ; * */ abstract void add(UserRecord ur); }
import java.util.Collection; abstract class UserDB { Collection<UserRecord> data; /* <pre> pre: // // There is no user record in the input UserDB with the same id as the // record to be added. // !exists (UserRecord ur_other ; data.contains(ur_other) ; ur_other.id.equals(ur.id)) && // // The id of the given user record is not empty and 8 characters or // less. // (ur.id != null) && (ur.id.length() > 0) && (ur.id.length() <= 8) && // // The email address is not empty. // (ur.email != null) && (ur.email.length() > 0) && // // If the phone area code and number are present, they must be 3 digits // and 7 digits respectively. // (if (ur.phone.area != 0) Integer.toString(ur.phone.area).length() == 3) && (if (ur.phone.number != 0) Integer.toString(ur.phone.number).length() == 7); post: // Same as above ; * */ abstract void add(UserRecord ur); }
(ur.email != null) && (ur.email.length() > 0)
import java.util.Collection; import java.util.List; abstract class UserDB { Collection<UserRecord> data; /** * Find a user or users by real-world name. If more than one is found, * the output list is sorted by id. * <pre> pre: // Not defined yet. ; post: // // The output list consists of all records of the given name in the // input data. // forall (UserRecord ur ; return.contains(ur) ; data.contains(ur) && ur.name.equals(name)) && // // The output list is sorted lexicographically by id, according to the // semantics of java.lang.String.compareTo(). // forall (int i ; (i >= 0) && (i < return.size() - 1) ; return.get(i).id.compareTo(return.get(i+1).id) < 0); * */ abstract List<UserRecord> findByName(String name); }
"For each position i in the output list, such that i is between the first and the second to the last positions in the list, the ith element of the list is less than the i+1st element of the list."
forall (int i ; \result.get(i).id.compareTo(\result.get(i+1).id) < 0)
import java.util.Collection; import java.util.List; abstract class UserDB { Collection<UserRecord> data; /** * Find a user or users by real-world name. If more than one is found, * the output list is sorted by id. * <pre> pre: // Not defined yet. ; post: recordsFound(name, return) && sortedById(return); * */ abstract List<UserRecord> findByName(String name); /** * Return true if the given list consists of all records of the given name * in this.data. * <pre> post: return == forall (UserRecord ur ; list.contains(ur) iff data.contains(ur) && ur.name.equals(name)); * */ abstract boolean recordsFound(String name, Collection<UserRecord> list); /** * Return true if the given list is sorted lexicographically by id, * according to the semantics of java.lang.String.compareTo(). * <pre> post: return == forall (int i ; (i >= 0) && (i < list.size() - 1) ; list.get(i).id.compareTo(list.get(i+1).id) < 0); * */ abstract boolean sortedById(List<UserRecord> list); }
/* * * This file defines the objects and operations related to maintaining the * user, group, and location databases of the calendar system. See Section 2.6 * of the Milestone 8 requirements. */ import java.util.Collection; import java.util.List; /** * UserDB is the repository of registered user information. */ abstract class UserDB { /** * The collection of user data records. */ Collection<UserRecord> data; /** * Reference to GroupDB needed for change and delete methods. */ GroupDB groupDB; /** * Add the given UserRecord to the given UserDB. The Id of the given user * record must not be the same as a user record already in the UserDB. * The Id component is required and must be eight characters or less. The * email address is required. The phone number is optional; if given, the * area code and number must be 3 and 7 digits respectively. * <pre> pre: // // There is no user record in the input UserDB with the same id as the // record to be added. // !exists (UserRecord ur_other ; data.contains(ur_other) ; ur_other.id.equals(ur.id)) && // // The id of the given user record is not empty and 8 characters or // less. // (ur.id != null) && (ur.id.length() > 0) && (ur.id.length() <= 8) && // // The email address is not empty. // (ur.email != null) && (ur.email.length() > 0) && // // If the phone area code and number are present, they must be 3 digits // and 7 digits respectively. // ((ur.phone.area != 0) ==> Integer.toString(ur.phone.area).length() == 3) && ((ur.phone.number != 0) ==> Integer.toString(ur.phone.number).length() == 7); post: // // A user record is in the output data if and only if it is the new // record to be added or it is in the input data. // forall (UserRecord ur_other ; (data'.contains(ur_other)) iff ur_other.equals(ur) || data.contains(ur_other)); * */ abstract void add(UserRecord ur); /** * Find a user by unique id. * <pre> post: // // If there is a record with the given id in the input data, then the // output record is equal to that record, otherwise the output record // is null. // exists (UserRecord ur_found ; data.contains(ur_found) ; ur_found.id.equals(id) && ur_found.equals(return)) || !exists (UserRecord ur_found ; data.contains(ur_found) ; ur_found.id.equals(id)) && return == null; * */ abstract UserRecord findById(String id); /** * Find a user or users by real-world name. If more than one is found, * then the output list is sorted by id. * <pre> post: // // The output list consists of all records of the given name in the // input data. // forall (UserRecord ur ; return.contains(ur) ; data.contains(ur) && ur.name.equals(name)) && // // The output list is sorted lexicographically by id, according to the // string comparison semantics of java.lang.String.compareTo(). // forall (int i ; (i >= 0) && (i < return.size() - 1) ; return.get(i).id.compareTo(return.get(i+1).id) < 0); * */ abstract List<UserRecord> findByName(String name); /** * Change the given old UserRecord to the given new record. The old and * new records must not be the same. The old record must already be in * the input db. The new record must meet the same conditions as for the * input to the AddUser operation. Typically the user runs the FindUser * operation prior to Change to locate an existing record to be changed. * * If the user record id is changed, then change all occurrences of the * old id in the group db to the new id. * <pre> pre: // // The old and new user records are not the same. // !old_ur.equals(new_ur) && // // The old record is in this.data. // data.contains(old_ur) && // // There is no user record in the input UserDB with the same id as the // new record to be added. // ! exists (UserRecord ur_other ; data.contains(ur_other) ; ur_other.id.equals(new_ur.id)) && // // The id of the new record is not empty and 8 characters or less. // (new_ur.id != null) && (new_ur.id.length() > 0) && (new_ur.id.length() <= 8) && // // The email address is not empty. // (new_ur.email != null) && (new_ur.email.length() > 0) && // // If the phone area code and number are present, they must be 3 digits // and 7 digits respectively. // ((new_ur.phone.area != 0) ==> Integer.toString(new_ur.phone.area).length() == 3) && ((new_ur.phone.number != 0) ==> Integer.toString(new_ur.phone.number).length() == 7); post: // // A user record is in the output data if and only if it is the new // record to be added or it is in the input data, and it is not the old // record. // forall (UserRecord ur_other ; data'.contains(ur_other) iff ur_other.equals(new_ur) || (data.contains(ur_other) && !ur_other.equals(old_ur))) && // // If new id is different than old id, then all occurrences of old id // in the GroupDB are replaced by new id. // !old_ur.id.equals(new_ur.id) ==> true // Logic left as exercise for the reader ; * */ abstract void change( UserRecord old_ur, UserRecord new_ur); /** * Delete the given user record from the given UserDB. The given record * must already be in the input db. Typically the user runs the FindUser * operation prior to Delete to locate an existing record to delete. * * In addition, delete the user from all groups of which the user is a * member. If the deleted user is the only leader of a one more groups, * output a warning indicating that those groups have become leaderless. * <pre> pre: // // The given user record is in this.data. // data.contains(ur); post: // // A user record is in the output data if and only if it is not the // existing record to be deleted and it is in the input data. // forall (UserRecord ur_other ; data'.contains(ur_other) iff !ur_other.equals(ur) && data.contains(ur_other)) && // // The id of the deleted user is not in the leader or member lists of // any group in the output GroupDB. (NOTE: This clause is not as // strong as a complete "no junk, no confusion" spec. Why not? Should // it be?) // forall (GroupRecord gr ; groupDB.data.contains(gr) ; !gr.leaders.contains(ur.id) && !gr.members.contains(ur.id)) && // // The LeaderlessGroupsWarning list contains the ids of all groups // whose only leader was the user who has just been deleted. // forall (GroupRecord gr ; groupDB.data.contains(gr) ; forall (String id ; (return.groupNames.contains(id) iff gr.leaders.size() == 1) && (gr.leaders.get(0).equals(ur.id)))); * */ abstract LeaderlessGroupsWarning delete(UserRecord ur); } /** * A UserRecord is the information stored about a registered user of the * Calendar Tool. The Name component is the user`s real-world name. The * Id is the unique identifier by which the user is known to the Calendar * Tool. The EmailAddress is the electronic mail address used by the * Calendar Tool to contact the user when necessary. The PhoneNumber is * for information purposes; it is not used by the Calendar Tool for * contacting the user. */ abstract class UserRecord { String name; String id; String email; PhoneNumber phone; } abstract class PhoneNumber { int area; int number; } /** * LeaderlessGroupsWarning is a secondary output of the UserDB.change and * UserDB.delete operations, indicating the names of zero or more groups that * have become leaderless as the result of a user having been deleted. */ abstract class LeaderlessGroupsWarning { Collection<String> groupNames; } /** * GroupDB is the repository of user group information. */ abstract class GroupDB { /** * The collection of group data records. */ Collection<GroupRecord> data; /** * Reference to GroupDB needed for change and delete methods. */ UserDB userDB; /** * Add the given GroupRecord to the given GroupDB. The name of the given * group must not be the same as a group already in the GroupDB. All * group members must be registered users. The leader(s) of the group * must be members of it. * <pre> pre: // // All group members are registered users. // forall (String id ; gr.members.contains(id) ; exists (UserRecord ur ; userDB.data.contains(ur) ; ur.id.equals(id))) && // // All group leaders are members of the group. // forall (String id ; gr.leaders.contains(id) ; gr.members.contains(id)); post: // // A group record is in the output db if and only if it is the new // record to be added or it is in the input db. // forall (GroupRecord gr_other ; data'.contains(gr_other) iff gr_other.equals(gr) || data.contains(gr_other)); * */ abstract void add(GroupRecord gr); /** * Delete the given group record from the given GroupDB. The given record * must already be in the input db. Typically the user runs the FindGroup * operation prior to Delete to locate an existing record to delete. * <pre> pre: // // The given GroupRecord is in the given GroupDB. // data.contains(gr); post: // // A group record is in the output db if and only if it is not the // existing record to be deleted and it is in the input db. // forall (GroupRecord gr_other ; data'.contains(gr_other) iff !gr_other.equals(gr) && data.contains(gr_other)); * */ abstract void delete(GroupRecord gr); /** * Change the given old GroupRecord to the given new record. The old and * new records must not be the same. The old record must already be in * the input db. The new record must meet the same conditions as for the * input to the AddGroup operation. Typically the user runs the FindGroup * operation prior to Change to locate an existing record to be changed. * <pre> pre: // // The old and new group records are not the same. // !old_gr.equals(new_gr) && // // All group members are registered users. // forall (String id ; new_gr.members.contains(id) ; exists (UserRecord ur ; userDB.data.contains(ur) && ur.id.equals(id))) && // // All group leaders are members of the group. // forall ( String id ; new_gr.leaders.contains(id) ; new_gr.members.contains(id)); post: // // A group record is in the output db if and only if it is the new // record to be added or it is in the input db, and it is not the old // record. // forall (GroupRecord gr_other ; data'.contains(gr_other) iff gr_other.equals(new_gr) || data.contains(gr_other) && !gr_other.equals(old_gr)); * */ abstract void change(GroupRecord old_gr, GroupRecord new_gr); /** * Find a group by unique name. * <pre> post: // // If there is a record with the given name in the input db, then the // output record is equal to that record, otherwise the output record // is empty. // exists (GroupRecord gr_found ; data.contains(gr_found) ; gr_found.name.equals(id) && gr_found.equals(return)) || !exists (GroupRecord gr_found ; data.contains(gr_found) ; gr_found.name.equals(id) && return == null); * */ abstract GroupRecord findById(String id); } /** * A GroupRecord is the information stored about a user group. The Name * component is a unique group name of any length. Leaders is a list of zero * or more users designated as group leader. Members is the list of group * members, including the leaders. Both lists consist of user id's. Normally * a group is required to have at least one leader. The only case that a * group becomes leaderless is when its only leader is deleted as a registered * user. */ abstract class GroupRecord { String name; List<String> leaders; List<String> members; } /** * The LocationDB contains the location records that provide information about * the locations at which items are scheduled. */ abstract class LocationDB { Collection<LocationRecord> data; } /** * A LocationRecord has a name and number which serve to identify where * the location is. Both fields are free-form strings and the Calendar * Tool enforces no constraints on their values. The Bookings component * is a list of the titles of the items that are scheduled in the * location. The Remarks component is a free-form text that can be used * to describe any other pertinent information about the room. */ abstract class LocationRecord { String name; String number; Bookings bookings; Remarks remarks; } abstract class Bookings { /* ... */ } abstract class Remarks { /* ... */ }