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); } }