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