import java.lang.reflect.Array; import java.util.NoSuchElementException; import java.util.Scanner; /** * This is an implementation of Assignment 12. * * @author Cheng Yu * @assignment ICS211 Homework TA12 * @date Apr 20, 2010 * @bugs None */ public class ChengJade12 { /** * The main entry point of the program. * * @param args The command line arguments (not used). */ public static void main(final String[] args) { final PhoneBook book = new PhoneBook(); final Scanner scanner = new Scanner(System.in); // Loop until the user selects quit, but always show the menu of options. try { while (true) { // Display the menu. System.out.println("\nChoose one of the four options:\n"); System.out.println(" 1) Type 'quit' to exit the program."); System.out.println(" 2) Type 'insert' to add a new contact."); System.out.println(" 3) Type 'delete' to delete a contact."); System.out.println(" 4) Type 'display' to show the phone book."); System.out.println(" 5) Type a name to show a phone number."); System.out.print("\nYour option: "); final String input = scanner.nextLine(); // Check for the quit command. if (input.equalsIgnoreCase("quit")) break; // Check for the insert command. if (input.equalsIgnoreCase("insert")) { System.out.print("\nPlease enter the name to add: "); final String name = scanner.nextLine(); System.out.print("Please enter phone number: "); final String number = scanner.nextLine(); if (!validPhoneNumber(number)) System.out.println("\nWarning: '" + number + "' does not look like a valid phone number."); // It is unlikely but possible that two different names will produce // the same hash code, but if they do it will cause a failure in this // implementation of the hash table. try { book.add(name, number); } catch (final RuntimeException e) { System.err.println("\nError: Cannot add '" + name + "' because another entry with a different\nname exists " + "in the hash table and produces the same hash code."); continue; } System.out.println("\nInserted '" + name + "' with phone number '" + number + "'."); continue; } // Check for the delete command. if (input.equalsIgnoreCase("delete")) { System.out.print("\nPlease enter the name to remove: "); final String name = scanner.nextLine(); try { book.remove(name); } catch (final NoSuchElementException e) { System.out.println("\nThe name '" + name + "' does not exist."); continue; } System.out.println("\nRemoved '" + name + "' from the phone book."); continue; } // Check for the display command. if (input.equalsIgnoreCase("display")) { System.out.println(String.format("\n%1$-10s%2$-20s%3$s", "#", "Name", "Phone Number")); System.out.print(book); continue; } // For all other cases, treat the input as a name to search for. final String number; try { number = book.getPhoneNumber(input); } catch (final NoSuchElementException e) { System.out.println("\nThe name '" + input + "' does not exist."); continue; } System.out.println("\n" + input + "'s phone number is " + number + "."); } } catch (final NoSuchElementException e) { // The user has canceled the program (probably with Ctrl-C. Exit normally. } System.exit(0); } /** * This method returns true if the specified phone number matches a typical * phone number pattern: "[#-[###-]]###-####". * * @param number The phone number. * @return True indicates the phone number is valid looking. */ private static boolean validPhoneNumber(final String number) { assert number != null; return number.matches("(\\d-)?(\\d{3}-)?\\d{3}-\\d{4}"); } } /** * This class implements a generic hash table. The items of the hash table must * be comparable with each other. */ class HashTable<T extends Comparable<T>> { private T[] myTable = newArray(3); /** * This method adds an item to the hash table. In the event of a collision, * the hash table is resized. If the item already exists in the hash table, * then it is replaced. The item cannot be null. * * @param o The item to add. */ public void add(final T value) { if (value == null) throw new NullPointerException("value"); final int index = getIndex(value, myTable.length); if (myTable[index] == null || myTable[index].compareTo(value) == 0) { myTable[index] = value; return; } // If two different names return the same hash code, there is no possible // way to rehash the table. For example, "2<" and "0z" return the same // hash code. If these two strings were added to the hash table, then the // rehash method would fail with a stack overflow due to infinite recursion. // Instead of failing in this way, we handle the condition more gracefully. if (myTable[index].hashCode() == value.hashCode()) throw new RuntimeException(); rehash(myTable.length * 2 + 1); add(value); } /** * This method searches for an item in the hash table. If the item is found, * the item is returned from the hash table. If the item is not found, then * null is returned. * * @param o The item to find. * @return The item from the hash table or null if the item is not found. */ public T find(final T value) { if (value == null) throw new NullPointerException("value"); final int index = getIndex(value, myTable.length); if (myTable[index] == null || myTable[index].compareTo(value) != 0) return null; return myTable[index]; } /** * This method removes an item from the hash table and returns true if * successful. If the item is not found, the method returns false. * * @param o The item. * @return True indicates the item was removed. */ public boolean remove(final T value) { if (value == null) throw new NullPointerException("value"); final int index = getIndex(value, myTable.length); if (myTable[index] == null || myTable[index].compareTo(value) != 0) return false; myTable[index] = null; return true; } /** * This method returns the string representation of the class. * * @return The string representation of the class. */ @Override public String toString() { final StringBuilder builder = new StringBuilder(); int j = 0; for (final T element : myTable) if (element != null) builder.append(String.format("%1$-10s%2$s\n", Integer.valueOf(++j), element)); builder.append('\n' + "The current table size is: " + myTable.length + '\n'); return builder.toString(); } /** * This method returns a hash code index for the specified object. The value * returned is some number between zero and one less than the specified size. * * @param o The object. * @param tableSize The table size. * @return The hash code index. */ private int getIndex(final T value, final int tableSize) { assert value != null; assert tableSize > 0; return Math.abs(value.hashCode()) % tableSize; } /** * Creates and returns an array of a specified length for the generic type. * * @param length The length of the array. * @return A new array of the generic type. */ @SuppressWarnings("unchecked") private T[] newArray(final int length) { assert length >= 0; return (T[])Array.newInstance(Comparable.class, length); } /** * This method rehashes the hash table for a new size. If there are collisions * while rehashing the table, then the method executes recursively with a * larger size. * * @param newSize The new size of the hash table. */ private void rehash(final int newSize) { assert newSize > myTable.length; final T[] newTable = newArray(newSize); for (final T value : myTable) { if (value == null) continue; final int index = getIndex(value, newSize); if (newTable[index] == null) { newTable[index] = value; continue; } rehash(newSize * 2 + 1); return; } myTable = newTable; } } /** * This class implements information that is entered into the telephone book. */ class PersonInfo implements Comparable<PersonInfo> { private final String myName; private final String myNumber; /** * This method compares two instances of class. The comparison is based only * on the name and not the number. * * @return 1 if greater, 0 if equal, and -1 if less than the specified value. */ public int compareTo(final PersonInfo person) { return person == null ? 1 : myName.compareToIgnoreCase(person.getName()); } /** * This constructor initializes the class data so that it is suitable for * searching for entries. The number is initializes to zero. * * @param name The name of the entry. */ public PersonInfo(final String name) { this(name, ""); } /** * This constructor initializes the class data. * * @param name The name of the entry. * @param number The phone number of the entry. */ public PersonInfo(final String name, final String number) { if (name == null) throw new NullPointerException("name"); if (number == null) throw new NullPointerException("number"); myName = name; myNumber = number; } /** * This method returns the name. * * @return The name. */ public String getName() { return myName; } /** * This method returns the number. * * @return The number. */ public String getNumber() { return myNumber; } /** * This method returns the hash code for the class. The code is based only on * the name and not the number. * * @return The hash code. */ @Override public int hashCode() { return myName.hashCode(); } /** * This method returns the string representation of the class. * * @return The string representation of the class. */ @Override public String toString() { return String.format("%1$-20s%2$s", myName, myNumber); } } /** * This class implements a phone book. */ class PhoneBook { private final HashTable<PersonInfo> book = new HashTable<PersonInfo>(); /** * This method adds an item to the phone book. * * @param name The name to add. * @param number The number to add. */ public void add(final String name, final String number) { if (name == null) throw new NullPointerException("name"); book.add(new PersonInfo(name, number)); } /** * This method searches for an item in the telephone book. If the item is * found, then the phone number is returned. If the item is not found, then * NoSuchElementException is thrown. * * @param name The name of the item to find. * @return The phone number or null if the item is not found. * @throws NoSuchElementException */ public String getPhoneNumber(final String name) { if (name == null) throw new NullPointerException("name"); final PersonInfo entryFound = book.find(new PersonInfo(name)); if (entryFound == null) throw new NoSuchElementException(); return entryFound.getNumber(); } /** * This method removes an item (based on a name) from the phone book. * * @param name The name of the item to remove. * @throws NoSuchElementException */ public void remove(final String name) { if (name == null) throw new NullPointerException("name"); if (!book.remove(new PersonInfo(name))) throw new NoSuchElementException(); } /** * This method returns the string representation of the class. * * @return The string representation of the class. */ @Override public String toString() { return book.toString(); } }