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 { /* ... */ }