Data Structures Abstraction and Design Using Java 3rd Edition Solution Manual
Data Structures Abstraction and Design Using Java 3rd Edition Solution Manual
Problem You have a client who wants to store a simple telephone directory in her computer
that she can use for storage and retrieval of names and numbers. She has a data file
that contains the names and numbers of her friends. She wants to be able to insert
new names and numbers, change the number for an entry, and retrieve selected tele-
phone numbers. She also wants to save any changes in her data file.
Input/Output Requirements
Earlier we discussed some questions that would have to be answered in order to
complete the specification of the requirements for the phone directory problem.
Most of the questions dealt with input and output considerations. We will list some
answers to these questions next.
INPUTS
Initial phone directory Each name and number will be read from separate
lines of a text file. The entries will be read in
sequence until all entries are read.
Additional entries Each entry is typed by the user at the keyboard when
requested.
OUTPUTS
Name and phone numbers The name and number of each person selected by the
program user are displayed on separate output lines.
Updated phone directory Each name and number will be written to separate
lines of a text file. The entries will be written in
sequence until all entries are written.
Analysis The first step in the analysis is to study the problem input and output requirements
carefully to make sure that they are understood and make sense. You can use a tool
called a use case to help you refine the system requirements.
Use Cases
A use case is a list of the user actions and system responses for a particular sub-
problem in the order that they are likely to occur.
The following four subproblems were identified for the telephone directory
program:
• Read the initial directory from an existing file
• Insert a new entry
• Edit an existing entry
• Retrieve and display an entry
The use case (Table 1.4) for the first subproblem (“Read the initial directory”)
shows that the user issues a single command and the system responds by either read-
ing a directory from a file or by creating an empty directory if there is no file. The
second use case (Table 1.5) is for the subproblems “Insert a new entry” and “Edit
an existing entry”. Because the names in the directory must be unique, inserting a
new entry and editing an existing entry require a search to determine whether the
name is already present. Thus, from the user’s point of view, the insert and edit
processes are the same. The last use case (Table 1.6) shows the user interaction for
the last subproblem (“Retrieve and display an entry”).
The steps shown in each use case flesh out the user interaction with the program.
The use cases should be reviewed by the client to make sure that your intentions are
the same as hers. For most of the problems we study in this book, the user interac-
tion is straightforward enough that use cases will not be required.
TA B L E 1 . 4
Use Case for Reading the Initial Directory
TA B L E 1 . 5
Use Case for Inserting a New Entry or Editing an Existing Entry
Step User’s Action System’s Response
1. User issues the command to
insert or change an entry.
2. System prompts for the name.
3. User enters name. If user cancels entry of name, process terminates.
4. System prompts for the number.
5. User enters number. If user cancels entry of number, process terminates.
6. The directory is updated to contain the new name and number.
If the name was not already in the directory, the user is notified that
a new name was entered. If the name already exists, the user is noti-
fied that the number was changed and is shown both the old and
new numbers.
TA B L E 1 . 6
Use Case for Retrieving and Displaying an Entry
PDApplication
main()
‹‹interface›› ‹‹interface››
PDUserInterface PhoneDirectory
sends command updates
processCommands() loadData()
addOrChangeEntry()
lookupEntry()
User removeEntry()
save()
The directory should be saved whenever the program is exited. The phone directory
has limited usefulness if updates to the directory cannot be saved from one run to
the next.
There is another way to split this problem into subproblems. Overall, we want a
phone directory application that combines a user interface as the front end and a
persistent (permanent) storage of data as the back end. Thus, Figure 1.6 can be
refined as shown in Figure 1.9. The black diamonds in Figure 1.9 indicate that a
PDApplication object has objects of type PDUserInterface and PhoneDirectory as its
components, and that they are created by the PDApplication object.
In Section 1.4, we identified two abstract data types: the PDUserInterface and the
PhoneDirectory. They are shown in the class diagram as interfaces. In Section 1.6
classes that implement these interfaces will be designed. By splitting the design
between the user interface and the directory, we can work on them independently.
As long as the requirements defined by the interfaces are met, the front-end user
interface does not care which back end it is dealing with, and the back-end direc-
tory does not care which front end it is dealing with.
PrintWriter A class in the Java API that provides output lines through a Writer object.
The first class in Table 1.7, PDApplication, contains a main method which starts
program execution. From the use case in Table 1.4, we know that this method must
create the PhoneDirectory object and read in the initial directory. Next, it must cre-
ate a PDUserInterface object that interacts with the user to determine which oper-
ations should be performed. The list of steps for method main follows.
PDApplication
User
main(sourceName)
new
PhoneDirectory
loadData()
new BufferedReader
readLine()
add()
new
PDUserInterface
processCommands(phoneDirectory)
The sequence diagram shows all the objects involved in this use case across the hor-
izontal axis, with each object’s type underlined. Time is shown along the vertical
axis. There is a dashed line coming down from each object that represents the
object’s life line. When a method of this object is called, the dashed line becomes a
solid line indicating that the method is executing. All interaction with an object is
indicated by a horizontal line that terminates at the object’s life line.
The PDApplication object is created when the application begins execution. Tracing
down its life line, you can see that its main method first sends the new message to a
class that implements the PhoneDirectory interface, creating a new PhoneDirectory
object. Next, main sends that object the loadData message. Method loadData is
described in the PhoneDirectory interface. Looking at the life line for this
PhoneDirectory object, you see that method loadData creates a new BufferedReader
object and sends it a readLine message. Next, loadData sends the add message to the
PhoneDirectory object. (Note that add is a new method that was not identified ear-
lier.) This is the same object as the one that received the loadData message, so this
add message is known as a message to self. Although the sequence diagram cannot
show looping, the process of reading lines and adding entries continues until there
are no remaining entries.
After all entries are read and saved, method main creates a new object (type
PDUserInterface) and sends it the processCommands message, passing it the
PhoneDirectory object as an argument. This provides the PDUserInterface object
the necessary access to the PhoneDirectory object to process the commands. This
completes the sequence diagram for reading the initial directory from a file. It shows
all the steps performed by method main, including calling processCommands after the
directory is loaded. Method processCommands will continue executing until the user
issues the “Exit program” command.
The sequence diagram (Figure 1.10) shows that method loadData of the
PhoneDirectory object performs most of the work for the “Read initial directory
data” use case. Method loadData calls all the methods shown after it on the life line
for the PhoneDirectory object.
CASE STUDY Designing a Telephone Directory Program (cont.)
PDApplication
main()
‹‹interface›› ‹‹interface››
PDUserInterface PhoneDirectory
sends command updates
processCommands() loadData()
addOrChangeEntry()
lookupEntry()
User removeEntry()
save()
ArrayBasedPD
+ loadData()
+ addOrChangeEntry()
+ lookupEntry()
+ removeEntry()
+ save()
DirectoryEntry
- String name
- String number
Constructor Behavior
public DirectoryEntry(String name, Creates a new DirectoryEntry with the specified name
String number) and number.
Method Behavior
public String getName() Retrieves the name.
public String getNumber() Retrieves the number.
public void setNumber(String number) Sets the number to the specified value.
TA B L E 1 . 9
Methods Declared in Interface PhoneDirectory
Method Behavior
public void loadData(String Loads the data from the data file whose name is given by
sourceName) sourceName.
public String addOrChangeEntry Changes the number associated with the given name to the
(String name, String number) new value, or adds a new entry with this name and number.
public String lookupEntry Searches the directory for the given name.
(String name)
public String removeEntry Removes the entry with the specified name from the direc-
(String name) tory and returns that person’s number or null if not in the
directory (left as an exercise).
public void save() Writes the contents of the array of directory entries to the
data file.
LISTING 1.2
PhoneDirectory.java
/** The interface for the telephone directory.
*/
public interface PhoneDirectory {
Method loadData
Method loadData is used to read the initial directory from a data file. The file name
is passed as an argument to loadData when it is called.
Method addOrChangeEntry
Method addOrChangeEntry is used to either add a new entry to the directory or
change an existing entry if the name is already in the directory. The name and num-
ber are passed as arguments to addOrChangeEntry.
Algorithm for Method addOrChangeEntry
1. Call method find to see whether the name is in the directory.
2. if the name is in the directory
3. Change the number using the setNumber method of the DirectoryEntry.
4. Return the previous value of the number.
else
5. Add a new entry using method add.
6. Return null.
Note that we have identified another new method, find, for class ArrayBasedPD.
Method lookupEntry
Method lookupEntry is passed a person’s name as an argument. It retrieves the per-
son’s number or null if the name is not found.
Method save
Method save creates an output file and then writes all information stored in the
array to this file. The file name is stored in data field sourceName. The algorithm for
the save method follows.
PDApplication
main()
‹‹interface›› ‹‹interface››
PDUserInterface PhoneDirectory
sends command updates
processCommands() loadData()
addOrChangeEntry()
lookupEntry()
User removeEntry()
save()
ArrayBasedPD
+ loadData()
+ addOrChangeEntry()
+ lookupEntry()
+ removeEntry()
+ save()
- add()
- find()
- reallocate()
DirectoryEntry
- String name
- String number
Implementation Next we write the code for class ArrayBasedPD. Listing 1.3 shows the data field dec-
larations for the class. We use the Javadoc style for commenting the data fields.
LISTING 1.3
Data Field Declarations for ArrayBasedPD.java
import java.io.*;
// Data Fields
...
}
TA B L E 1 . 1 1
Private Methods of ArrayBasedPD class
Private Method Behavior
private int find(String name) Searches the array of directory entries for the name.
private void add(String name, Adds a new entry with the given name and number to the array
String number) of directory entries.
private void removeEntry(int index) Removes the entry at the given index from the directory array.
private void reallocate() Creates a new array of directory entries with twice the capacity
of the current one.
LISTING 1.4
Method loadData for ArrayBasedPD.java
/** Method to load the data file.
pre: The directory storage has been created and it is empty.
If the file exists, it consists of name-number pairs
on adjacent lines.
post: The data from the file is loaded into the directory.
@param sourceName The name of the data file
*/
public void loadData(String sourceName) {
// Remember the source name.
this.sourceName = sourceName;
try {
// Create a BufferedReader for the file.
BufferedReader in = new BufferedReader(
new FileReader(sourceName));
String name;
String number;
// Read each name and number and add the entry to the array.
while ((name = in.readLine()) != null) {
// Read name and number from successive lines.
if ((number = in.readLine()) == null) {
break; // No number read, exit loop.
}
// Add an entry for this name and number.
add(name, number);
}
// Close the file.
in.close();
} catch (FileNotFoundException ex) {
// Do nothing — no data to load.
return;
} catch (IOException ex) {
System.err.println(“Load of directory failed.”);
ex.printStackTrace();
System.exit(1);
}
}
The readLine method of the BufferedReader class reads a line and returns it as a
String object. If there is no more data to be read, null is returned, so we exit the
while loop. Note that we combined the assignment statement and the test for null
in the while statement condition
while ((name = in.readLine()) != null) {...}
Similarly we combined the assignment and test when reading the number in the if
condition
if ((number = in.readLine()) == null) {
break; // No number read, exit loop.
}
Therefore, we also exit the loop (through execution of a break statement) if a name
was read but a number was not. If both a name and number are read, then a new
entry is added to the directory and we continue reading and adding entries.
If a file with name sourceName is not found, we immediately return (no data to read).
(Later, we will write the new directory to file sourceName.) If an input/output error
occurs, a stack trace is displayed and we exit the program with an exit code of 1,
indicating an error.
PROGRAM STYLE
Use of Assignment in a Condition and Use of break
The while loop in this section uses two features of Java that simplify the code but are
considered controversial. Some programmers prefer not to combine assignment with the
evaluation of a condition. However, in this case, it simplifies the code to do it this way.
Also, the requirement to exit the loop without storing an entry is met very naturally
using the break statement. Some programmers prefer to provide only one way to exit a
loop: when the while condition fails.
LISTING 1.5
Method save for ArrayBasedPD.java
/** Method to save the directory.
pre: The directory has been loaded with data.
post: Contents of directory written back to the file in the
form of name-number pairs on adjacent lines.
modified is reset to false.
*/
public void save() {
if (modified) { // If not modified, do nothing.
try {
// Create PrintWriter for the file.
PrintWriter out = new PrintWriter(
new FileWriter(sourceName));
PITFALL
Returning –1 (Failure) Before Examining All Array Elements
A common logic error is to code the search loop for method find as follows:
for (int i = 0; i < size; i++) {
if (theDirectory[i].getName().equals(name)) {
return i;
} else {
return –1; // Incorrect! – tests only one element.
}
}
This loop incorrectly returns a result after testing just the first element.
41
Analysis Through the description of the interface, we know that a class that implements
PDUserInterface must contain a public method, processCommands, declared as fol-
lows in the interface:
void processCommands(PhoneDirectory theDirectory);
The interface enables clients to use method processCommands without knowing the
details of its implementation (information hiding). We will introduce new private
methods that are called by processCommands to perform its tasks, but are unavailable
to a client.
The kind of user interaction that will take place will be determined by the input/out-
put facilities used in a class that implements the interface. Three options would be
console input, GUI input using a specially designed GUI for this problem, and GUI
input using JOptionPane dialog windows. We will write classes that use the first and
last options. (If you are unfamiliar with any of these Java input/output features,
review Appendix A.)
For both classes, method processCommands should present a menu of choices to the
user:
• Add or Change an Entry
• Look Up an Entry
• Remove an Entry
• Save the Directory Data
• Exit the Program
Design For both classes, method processCommands will use a “menu-driven” loop to control
the interaction with the user. In a true GUI, a loop would not be necessary. After
each command is processed, the menu of choices is displayed again. This process
continues until the user selects “Exit the program”.
do {
// Get the action to perform from the user.
// The user’s choice will be a number from 0 through 4.
. . .
switch (choice) {
case 0: doAddChangeEntry(); break;
case 1: doLookupEntry(); break;
case 2: doRemoveEntry(); break;
case 3: doSave(); break;
case 4: doSave(); break;
}
} while (choice < commands.length - 1);
The method processCommands calls a private method shown in the foregoing switch
statement to perform the user’s choice. Note that method doSave is called by the last
two cases. We discuss the design and coding of these methods next.
Implementation The PDGUI Class
Our first class will interact with the user through a GUI. It uses method
showOptionDialog of the JOptionPane class, which is part of the Java Swing API, to
present the menu to the user, request data from the user, and display the results to
the user. The initial menu is as follows:
Method doAddChangeEntry
The doAddChangeEntry method uses the JOptionPane.showInputDialog method to
request the name and new number. Here are examples of these dialogs:
If the user selects Cancel, a null string is returned. In that case the method will return
immediately without changing the directory.
The PhoneDirectory.addOrChangeEntry method is called if values are entered for the
name and number. A return value of null indicates that this is a new entry, and a
confirmation dialog is displayed by JOptionPane.showMessageDialog:
If the name was already in the directory, the previous value of the number is
returned, and the confirmation shows both the old and the new number, as follows:
Algorithm for Method doAddChangeEntry
1. Read the name of the entry.
2. Read the number of the entry.
3. Send the addOrChangeEntry message to the PhoneDirectory object.
4. if the result of addOrChangeEntry was null
5. The message “name was added to the directory” is displayed and the
new number is displayed.
else
6. The message “number for name was changed”, and the old and new
number are displayed.
Method doLookupEntry
The doLookupEntry method uses the same dialog as doAddChangeEntry to request the
name. If the user cancels the dialog, the method returns. Otherwise the number is
looked up by calling the PhoneDirectory.lookupEntry method, and the result is dis-
played. If the name is not in the directory, a message is displayed. Examples of both
are as follows:
Method doSave
The doSave method calls the save method of the PhoneDirectory object.
Listing 1.6 shows the code for the PDGUI class. It implements all the methods dis-
cussed above.
LISTING 1.6
PDGUI.java
import javax.swing.*;
// Methods
/** Method to display the command choices and process user
commands.
pre: The directory exists and has been loaded with data.
post: The directory is updated based on user commands.
@param thePhoneDirectory A reference to the PhoneDirectory
to be processed.
*/
public void processCommands(PhoneDirectory thePhoneDirectory) {
theDirectory = thePhoneDirectory;
int choice;
do {
choice = JOptionPane.showOptionDialog(
null, // No parent
"Select a Command", // Prompt message
"PhoneDirectory", // Window title
JOptionPane.YES_NO_CANCEL_OPTION, // Option type
JOptionPane.QUESTION_MESSAGE, // Message type
null, // Icon
commands, // List of commands
commands[commands.length - 1]); // Default choice
switch (choice) {
case 0: doAddChangeEntry(); break;
case 1: doLookupEntry(); break;
case 2: doRemoveEntry(); break;
case 3: doSave(); break;
case 4: doSave(); break;
}
} while (choice < commands.length - 1);
System.exit(0);
}
/** Method to add or change an entry.
pre: The directory exists and has been loaded with data.
post: A new name is added, or the value for the name is
changed, modified is set to true.
*/
private void doAddChangeEntry() {
// Request the name
String newName = JOptionPane.showInputDialog("Enter name");
if (newName == null) {
return; // Dialog was cancelled.
}
// Request the number
String newNumber = JOptionPane.showInputDialog("Enter number");
if (newNumber == null) {
return; // Dialog was cancelled.
}
// Insert/change name-number
String oldNumber = theDirectory.addOrChangeEntry(newName,
newNumber);
String message = null;
if (oldNumber == null) { // New entry.
message = newName + " was added to the directory"
+ "\nNew number: " + newNumber;
} else { // Changed entry.
message = "Number for " + newName + " was changed "
+ "\nOld number: " + oldNumber
+ "\nNew number: " + newNumber;
}
// Display confirmation message.
JOptionPane.showMessageDialog(null, message);
}
When this method runs, make sure you test all possible commands. Try exiting with-
out first saving the directory and verify that the file is correctly updated and saved.
Implementation The PDConsoleUI Class
Listing 1.7 shows the code for the PDConsoleUI class. This class uses System.out to
display the menu of choices and results. It also uses System.in to read data from the
user.
The constructor creates a BufferedReader object that is attached to System.in. This
object has a readLine method, which reads a line of input and returns it as a String.
Method processCommands
The readLine method may throw an IOException, so the processCommands method
uses a try–catch block to enclose the processing of user commands. Each of the indi-
vidual methods that process the commands is declared to throw an IOException.
Thus they do not need a try–catch block. (We provide thorough coverage of excep-
tions in Chapter 2.)
Should an IOException be thrown, an error message will be written to System.err
and the program will exit with an exit code of 1 (error).
The initial menu is as follows:
Method doAddChangeEntry
The doAddChangeEntry method requests the name followed by the number. The
PhoneDirectory.addOrChangeEntry method is called after values are entered for the
name and number. A return value of null indicates that this is a new entry, and a
confirmation dialog is displayed as follows:
If the name was already in the directory, the previous value of the number is
returned, and the confirmation shows both the old and new number as follows:
Method doLookupEntry
The doLookupEntry method uses the same prompt as doAddChangeEntry to request
the name. If the user cancels the data entry, the method returns. Otherwise the num-
ber is looked up by calling the PhoneDirectory.lookupEntry method, and the result
is displayed. If the name is not in the directory, a message is displayed. Examples of
both cases follow:
Method doSave
The doSave method calls the save method of the PhoneDirectory.
LISTING 1.7
PDConsoleUI.java
import java.io.*;
// Constructor
/** Default constructor. */
public PDConsoleUI() {
in = new BufferedReader(new InputStreamReader(System.in));
}
// Methods
/** Method to display the command choices and process user
commands.
pre: The directory exists and has been loaded with data.
post: The directory is updated based on user commands.
@param thePhoneDirectory A reference to the PhoneDirectory
to be processed
*/
public void processCommands(PhoneDirectory thePhoneDirectory) {
String[] commands = {"Add/Change Entry",
"Look Up Entry",
"Remove Entry",
"Save Directory",
"Exit"};
theDirectory = thePhoneDirectory;
int choice;
try {
do {
for (int i = 0; i < commands.length; i++) {
System.out.println("Select " + i + ": "
+ commands[i]);
}
Koffman Chapt 01 6/11/04 6:00 PM Page 52