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 Dec 4, 2008
* @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();
}*/
public String toString() {
final StringBuilder builder = new StringBuilder();
int j = 0;
for (final T element : myTable)
if (element != null) {
builder.append(Integer.valueOf(++j) + "\t");
builder.append(element + "\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();
}
}