import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.NoSuchElementException;
import java.util.Scanner;
/**
* This class provides a main method used to get input from the user. This class
* constructs an address book by calling the constructor with a string from the
* command line that specifies the file name, prints the contents of the
* database using toString, then accepts input from the user. The input is of
* four kinds:
* <ul>
* <li>the string "quit", which exits the program.</li>
* <li>the string "save", which saves the database back to the file from which
* the database was originally loaded.</li>
* <li>an email address containing '@', is a request to print all the entries
* matching the given email address.</li>
* <li>a name, which is an arbitrary string, means remove that name from the
* list.</li>
* </ul>
*
* @author Cheng Yu
* @assignment ICS211 TA Homework 5
* @date Oct 8, 2008
* @bugs None
*/
public class ChengJade05 {
/**
* The main entry point of the program.
*
* @param args
* The command line arguments.
*/
public static void main(final String[] args) {
// Check there is a path on the command line.
if (args.length != 1) {
System.err.println("Usage: ChengJade05 path");
System.exit(1);
}
// Create a phone directory by calling the constructor and load the
// contents from the specified file.
try {
final String path = args[0];
final Directory directory = new Directory(path);
// Start a loop that responds to user requests.
while (true) {
// Read the standard input using System.in.
final Scanner scanner = new Scanner(System.in);
// Print the contents of the database using toString of the directory
// object.
System.out.println("Directory Contents:\n");
System.out.println(directory);
// Print out the instructions.
System.out.println("Choose one of the four options:\n");
System.out.println(" 1) Type 'quit' to exit the program.");
System.out.println(" 2) Type 'save' to save the database.");
System.out.println(" 3) Type an email address containing '@' to find matching entries.");
System.out.println(" 4) Type a name to remove.");
System.out.print("\nYour option: ");
System.out.flush();
// Check there is a line ready to read from standard input, read the
// line, and ignore blank lines.
if (!scanner.hasNextLine())
break;
final String line = scanner.nextLine();
if (line.length() == 0) {
System.out.println("Ignoring blank line.\n");
continue;
}
// If the user types 'quit' then stop looping and exit.
if (line.compareToIgnoreCase("quit") == 0)
break;
// If the user types 'save' then re-save the address book to the
// same file.
if (line.compareToIgnoreCase("save") == 0) {
directory.saveToFile(path);
System.out.println("Done saving.\n");
continue;
}
// If the user types a string containing '@' then find and
// display the matching entries.
if (line.contains("@")) {
System.out.println(directory.findEmail(line));
continue;
}
// The user entered a string. Remove all entries that have the
// name and count the number of entries removed.
final int count = directory.remove(line);
if (count == 0) {
System.out.println("No such entry to remove.\n");
continue;
}
System.out.println("Done removing. The number of entries removed is "
+ count + "\n");
}
} catch (final ProgramException e) {
// In case of any errors, display the message, and exit with a
// failure.
System.err.println(e.getMessage());
System.exit(1);
}
// Return zero to let the eclipse debugger close happily.
System.exit(0);
}
}
/**
* An address class.
*/
class Node {
private final String myEmail;
private final String myName;
private Node myNext;
private final long myPhone;
/**
* Initializes a new instance of the class.
*
* @param name
* The name stored in this class.
* @param phone
* The phone number stored in this class.
* @param email
* The email address stored in this class.
*/
public Node(final String name, final long phone, final String email) {
assert name != null;
assert email != null;
myName = name;
myPhone = phone;
myEmail = email;
myNext = null;
}
/**
* Compares this address node's email to another email. The result is an
* integer compatible with other compareTo methods. The string comparisons are
* case insensitive.
*
* @param email
* The other email.
* @return An integer compatible with other compareTo methods.
*/
public int compareEmailTo(final String email) {
assert email != null;
return myEmail.compareToIgnoreCase(email);
}
/**
* Compares this address node to another address node. The comparison is made
* between the names of the two nodes and no other fields. The result is an
* integer compatible with other compareTo methods. The string comparisons are
* case insensitive.
*
* @param other
* The other address.
* @return An integer compatible with other compareTo methods.
*/
public int compareTo(final Node other) {
if (other == null)
return 1;
return compareTo(other.myName);
}
/**
* Compares this address node to a name. The result is an integer compatible
* with other compareTo methods. The string comparisons are case insensitive.
*
* @param name
* The other name.
* @return An integer compatible with other compareTo methods.
*/
public int compareTo(final String name) {
assert name != null;
return myName.compareToIgnoreCase(name);
}
/**
* Returns the email address.
*
* @return The email address.
*/
public String email() {
return myEmail;
}
/**
* Returns the name.
*
* @return The name.
*/
public String name() {
return myName;
}
/**
* Returns the pointer to the next node.
*
* @return The pointer to the next node.
*/
public Node next() {
return myNext;
}
/**
* Returns the phone number.
*
* @return The phone number.
*/
public long phone() {
return myPhone;
}
/**
* Sets the pointer to the next node.
*
* @param next
* The next node.
*/
public void setNext(Node next) {
myNext = next;
}
/**
* Returns the string representation of the class data.
*
* @return The string representation of the class data.
*/
@Override
public String toString() {
return myName + '\n' + myPhone + '\n' + myEmail;
}
}
/**
* A directory class.
*/
class Directory {
// Define the head node. The linked list is empty when the head node is null.
private Node myHead = null;
/**
* Initializes a new instance of the class. The directory is read from the
* specified file.
*
* @param path
* The path to the file used to initializes the database.
* @throws ProgramException
* Thrown if there is a problem reading the file.
*/
public Directory(final String path) throws ProgramException {
assert path != null;
// Open the file for reading with a scanner.
FileReader file = null;
try {
file = new FileReader(path);
} catch (FileNotFoundException e) {
throw new ProgramException("Unable to open the file " + path, e);
}
final Scanner scanner = new Scanner(file);
// Loop until there are no more lines. Create and add a node in each
// iteration of the loop.
while (scanner.hasNextLine()) {
String phoneText = null;
try {
final String name = scanner.nextLine();
phoneText = scanner.nextLine();
final long phone = Long.parseLong(phoneText);
final String email = scanner.nextLine();
add(new Node(name, phone, email));
} catch (NoSuchElementException e) {
throw new ProgramException("Unexpected end of file " + path, e);
} catch (NumberFormatException e) {
assert phoneText != null;
throw new ProgramException("Invalid phone number: " + phoneText, e);
}
}
// Close the file immediately (do not wait for garbage collection).
scanner.close();
try {
file.close();
} catch (IOException e) {
throw new ProgramException("Unable to close the file " + path, e);
}
}
/**
* Adds an address to the directory.
*
* @param nodeToAdd
* The new address entry to add to the directory.
*/
public void add(final Node nodeToAdd) {
assert nodeToAdd != null;
// Check if the directory is empty or if this item belongs at the head
// of the directory. If so, add the node at the head of the directory.
final String name = nodeToAdd.name();
assert name != null;
if (myHead == null || myHead.compareTo(nodeToAdd) >= 0) {
nodeToAdd.setNext(myHead);
myHead = nodeToAdd;
return;
}
// Loop over nodes until either the end of the directory is reached or
// the node belongs alphabetically before the next node.
Node nodeInList = myHead;
while (nodeInList.next() != null && nodeInList.next().compareTo(nodeToAdd) < 0)
nodeInList = nodeInList.next();
nodeToAdd.setNext(nodeInList.next());
nodeInList.setNext(nodeToAdd);
}
/**
* Finds entries for the specified email address. Each item is put into a list
* similar to the one returned by toString().
*
* @param email
* The email address.
* @return A list of entries with the specified email address.
*/
public String findEmail(final String email) {
assert email != null;
final StringBuilder builder = new StringBuilder();
// Start from the head and loop over to the end of the directory.
for (Node node = myHead; node != null; node = node.next()) {
// If the email address is a match, add it to the result.
if (node.compareEmailTo(email) == 0)
builder.append(node.toString() + '\n');
}
// If nothing added to the result, return a message. Otherwise return
// the contents.
final String result = builder.toString();
return (result.length() == 0) ? "No such email address found.\n" : result;
}
/**
* Removes items from the directory with the specified name. If more than one
* item is found, then all items are removed.
*
* @param name
* The name of the item to remove from the list.
* @return The number of items removed.
*/
public int remove(final String name) {
assert name != null;
// Do nothing if there are no items in the linked list.
if (myHead == null)
return 0;
// Check to remove the head of the list. If the head is removed, use
// recursion to search for other names to remove as well.
if (myHead.compareTo(name) == 0) {
myHead = myHead.next();
return 1 + remove(name);
}
// Loop over the remaining items. Check each next node for a matching name,
// and if the name is a match, then remove that node and use recursion to
// search for other names to remove.
for (Node node = myHead; node.next() != null; node = node.next()) {
if (node.next().compareTo(name) == 0) {
node.setNext(node.next().next());
return 1 + remove(name);
}
}
// Arriving here indicates no items were removed from the directory.
return 0;
}
/**
* Saves the contents of the directory the specified file. The items are saved
* in alphabetical order based on the name.
*
* @param path
* The path to the file.
* @throws ProgramException
* Thrown if there is a problem writing to the file.
*/
public void saveToFile(final String path) throws ProgramException {
assert path != null;
try {
// Open the file for writing, write the contents of the directory by
// calling toString, and close the writer. Write nothing if the directory
// is empty.
final FileWriter writer = new FileWriter(path);
if (myHead != null)
writer.write(toString());
writer.close();
} catch (final IOException e) {
throw new ProgramException("Error writing to file " + path, e);
}
}
/**
* Returns the string representation of the class data.
*
* @returns The string representation of the class data.
*/
@Override
public String toString() {
// Return a special message for no items in the directory.
if (myHead == null)
return "The database is empty.\n";
// Loop over each item in the directory. Each iteration, build a
// list for each directory entry, and then return that list as a string.
final StringBuilder builder = new StringBuilder();
for (Node node = myHead; node != null; node = node.next())
builder.append(node.toString() + "\n");
return builder.toString();
}
}
/**
* An exception class that is thrown and caught by the program.
*/
@SuppressWarnings("serial")
class ProgramException extends Exception {
/**
* Initializes a new instance of the class.
*
* @param message
* The message.
*/
public ProgramException(final String message) {
super(message);
}
/**
* Initializes a new instance of the class.
*
* @param message
* The message.
* @param cause
* The cause.
*/
public ProgramException(final String message, Throwable cause) {
super(message, cause);
}
}