Programming Assignment 2

A Simple Address Book

Example Implementation

/**
 * Implementation of Assignment 2: A simple address book.
 *
 * @author     Cheng, Jade
 * @assignment CSCI 2912 Assignment 2
 * @date       Feb, 26, 2012
 */

import java.util.ArrayList;
import java.util.NoSuchElementException;
import java.util.Scanner;

/**
 * Implementation of Assignment 2: A simple address book.
 */
public final class ChengJade2 {

	/**
	 * Adds a contact to the specified {@link AddressBook}.
	 * 
	 * @param book The {@link AddressBook} to which the contact is added.
	 * @param scanner The scanner reading from standard input.
	 * @throws NoSuchElementException Thrown if standard input is closed.
	 */
	private static void add(final AddressBook book, final Scanner scanner) {
		assert null != book;
		assert null != scanner;

		// Prompt for the name and phone number; then create a contact
		// and add it to the address book.
		final String name = prompt(scanner, "    NAME ---> ");
		final String phone = prompt(scanner, "    PHONE --> ");
		final Contact contact = new Contact(name, phone);
		book.add(contact);

		// Print some information about what was added.
		System.out.println();
		System.out.print("Added ");
		System.out.println(contact);
		System.out.println();
	}

	/**
	 * Finds contacts in the specified {@link AddressBook}.
	 * 
	 * @param book The {@link AddressBook} through which contacts are searched.
	 * @param scanner The scanner reading from standard input.
	 * @throws NoSuchElementException Thrown if standard input is closed.
	 */
	private static void find(final AddressBook book, final Scanner scanner) {
		assert null != book;
		assert null != scanner;

		// Prompt for what to find; then find the contacts.
		final String needle = prompt(scanner, "    WHAT --> ");
		final ArrayList<Contact> contacts = book.find(needle);

		// Print some information about what was found.
		if (!contacts.isEmpty())
			System.out.println();
		for (final Contact contact : contacts)
			System.out.println(contact);
		System.out.println();
		System.out.print("Number of contacts found: ");
		System.out.println(contacts.size());
		System.out.println();
	}

	/**
	 * The main entry point of the program.
	 * 
	 * @param args The command-line arguments.
	 */
	public static void main(final String[] args) {
		assert null != args;

		// Create a new address book.
		final AddressBook book = new AddressBook();

		// Read commands from standard input.
		final Scanner scanner = new Scanner(System.in);

		// Logic within this loop will determine the end case.
		while (true) {

			// Print a menu of available commands.
			System.out.println("   ~ Address Book Menu ~");
			System.out.println("+-------------------------+");
			System.out.println(" ADD     Adds a contact");
			System.out.println(" FIND    Finds contacts");
			System.out.println(" PRINT   Prints contacts");
			System.out.println(" QUIT    Quits");
			System.out.println("+-------------------------+");
			System.out.println();

			// Check if standard input is closed.
			try {

				// Get a command from the user.
				final String command = prompt(scanner, "--> ");

				// Add, find, print, quit, or process an invalid command.
				if ("add".equalsIgnoreCase(command))
					add(book, scanner);
				else if ("find".equalsIgnoreCase(command))
					find(book, scanner);
				else if ("print".equalsIgnoreCase(command))
					print(book);
				else if ("quit".equalsIgnoreCase(command))
					return;
				else
					System.out.println("\nInvalid command.\n");

			} catch (NoSuchElementException e) {
				// If the user closes standard input, terminate without
				// displaying an error.
				break;
			}
		}
	}

	/**
	 * Prints the {@link AddressBook} instance.
	 * 
	 * @param book The {@link AddressBook} to print.
	 */
	private static void print(final AddressBook book) {
		assert null != book;

		System.out.println();
		System.out.println(book);
	}

	/**
	 * Prompts the user for a string.
	 * 
	 * @param scanner The scanner reading standard input.
	 * @param message The message to display before the prompt.
	 * @return The string entered by the user.
	 * @throws NoSuchElementException Thrown if standard input is closed.
	 */
	private static String prompt(final Scanner scanner, final String message) {
		assert null != scanner;
		assert null != message;

		// Logic within this loop will determine the end case.
		while (true) {

			// Print the message and flush standard output.
			System.out.print(message);
			System.out.flush();

			// If the next line from the scanner has some length, return it;
			// otherwise, iterate.
			final String line = scanner.nextLine().trim();
			if (!line.isEmpty())
				return line;
		}
	}
}

/**
 * A simple address book that manages a collection of {@link Contact} instances.
 */
final class AddressBook {

	/** The list of {@link Contact} instances. */
	private final ArrayList<Contact> contacts = new ArrayList<>();

	/**
	 * Adds an {@link Contact} to this instance.
	 * 
	 * @param contact The {@link Contact} to add to this instance.
	 */
	public void add(final Contact contact) {
		assert null != contact;
		this.contacts.add(contact);
	}

	/**
	 * Finds a list of {@link Contact} instances that match the specified text.
	 * 
	 * @param needle The text to find.
	 * @return A list of {@link Contact} instances that match the specified
	 *         text.
	 */
	public ArrayList<Contact> find(final String needle) {
		assert null != needle;
		assert !needle.isEmpty();

		// Put the text in lower case; the match method expects this.
		final String needleLowerCase = needle.toLowerCase();

		// Create a new list of contacts.
		final ArrayList<Contact> list = new ArrayList<>();

		// Loop over every item in this instance; if the item matches the text,
		// then add it to the list.
		for (final Contact item : this.contacts)
			if (item.match(needleLowerCase))
				list.add(item);

		// Return the list of contacts that match the specified text.
		return list;
	}

	@Override
	public String toString() {
		// Build a string into this instance.
		final StringBuilder builder = new StringBuilder();

		// Add every contact in this instance.
		for (final Contact contact : this.contacts)
			builder.append(String.format("%s\n", contact));

		// Also write the number of contacts.
		if (!this.contacts.isEmpty())
			builder.append('\n');
		builder.append("Number of contacts: ");
		builder.append(this.contacts.size());
		builder.append('\n');

		// Return the string.
		return builder.toString();
	}
}

/**
 * An immutable class that contains information about a contact in an
 * {@link AddressBook} instance.
 */
final class Contact {

	/** The name associated with this instance. */
	public final String name;

	/** The phone number associated with this instance. */
	public final String phone;

	/**
	 * Initializes a new instance of the {@link Contact} class.
	 * 
	 * @param name The name associated with this instance.
	 * @param phone The phone number associated with this instance.
	 */
	public Contact(final String name, final String phone) {
		assert null != name;
		assert !name.isEmpty();
		assert null != phone;
		assert !phone.isEmpty();

		this.name = name;
		this.phone = phone;
	}

	/**
	 * Returns <code>true</code> if this instance should be included in the
	 * search results for the specified text; otherwise, <code>false</code>.
	 * 
	 * @param needle The text for which the user is searching. The text should
	 *          be in all lower-case letters.
	 * @return The value <code>true</code> if this instance should be included
	 *         in the search results for the specified text; otherwise,
	 *         <code>false</code>.
	 */
	public boolean match(final String needle) {
		assert null != needle;
		assert !needle.isEmpty();
		assert needle.toLowerCase().equals(needle);

		// Return true if either name or phone match the specified value. Note it
		// is assumed the specified value is lower case. Also note it would be
		// more efficient to cache the lower-case versions of name and phone as
		// fields.
		return false
				|| this.name.toLowerCase().contains(needle)
				|| this.phone.toLowerCase().contains(needle);
	}

	@Override
	public String toString() {
		// Note it would be more efficient to cache this description as a field.
		return String.format("[%s; %s]", this.name, this.phone);
	}
}