# Yhteystiedot ja osoitekirja 1/3 Tämä harjoitus pohjautuu seuraavaan kahteen luokkaan sekä erikseen toteutettavaan `AddressBookApp`-luokkaan. **Contact-luokka:** ``` // tiedosto Contact.java public class Contact { private String name; private String email; private String phone; public Contact(String name, String email, String phone) { this.name = name; this.email = email; this.phone = phone; } public String getName() { return this.name; } public String toString() { // Haluttu muoto: "Maija Meikäläinen (email: foo@bar.fi, phone: 5555)" return this.name + " (email: " + this.email + ", phone: " + this.phone + ")"; } } ``` **AddressBook-luokka:** ``` // tiedosto AddressBook.java import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; public class AddressBook { private List contacts; public AddressBook() { this.contacts = new ArrayList<>(); } public boolean add(Contact newContact) { this.contacts.add(newContact); return true; } public Contact search(String keyword) { for (Contact current : this.contacts) { String name = current.getName(); if (name != null && name.toLowerCase().contains(keyword.toLowerCase())) { return current; // palautetaan löytynyt arvo heti } } return null; // palautetaan null, jos ei löytynyt } @Override public String toString() { // TODO: toteuta tämä metodi. Metodin tulee muodostaa merkkijono, // joka sisältää kaikki yhteystiedot omilla riveillään. return "TODO"; } } ``` ## Ohjelman komentorivikäyttöliittymä Tehtävänä on tällä kertaa rakentaa uusi ohjelmaluokka `AddressBookApp`, joka mahdollistaa yhteystietojen käsittelyn `AddressBook` ja `Contact`-olioiden kanssa seuraavilla komennoilla: ``` This is an address book application. Available commands: list help add , , search exit ``` Ohjelma tulee jakaa osiin siten, että vain `AddressBookApp`-luokka pyytää käyttäjältä syötteitä ja tulostaa tekstiä. `Contact` ja `AddressBook` ovat vain datan varastointia ja käsittelyä varten. ## Syötteiden pyytäminen Tämän sovelluksen käyttöliittymässä syötettä pyydettäessä ruudulle tulostetaan `>` -merkki syötteen odottamisen merkiksi: ``` System.out.print("> "); ``` Koska `>`-merkki tulostettiin `print` eikä `println`-metodilla, syöttää käyttäjä komentonsa samalle riville. Käyttäjän komento saattaa koostua yhdestä tai useasta osasta. Yhden sanan komentoja ovat esimerkiksi: ``` > list ``` ja ``` > exit ``` Moniosaisia komentoja ovat puolestaan esimerkiksi: ``` > add Maija Meikäläinen, maija@example.com, +35850555556 ``` ``` > search Matti ``` ## Syötteiden lukeminen Scannerilla Syötteet kannattaa tässä sovelluksessa lukea kahdessa osassa. Ensin luetaan syötteen ensimmäinen sana ja sen jälkeen koko loppurivi: ``` boolean running = true; while (running) { System.out.print("> "); String command = input.next(); String theRest = input.nextLine().trim(); // ... toimintalogiikka } ``` Rivin ensimmäinen sana on aina komento (`command`), jonka perusteella valitaan mikä operaatio osoitekirjalle suoritetaan. Mahdollisesti annettua loppuriviä käytetään `add`- ja `search`-komentojen tapauksessa uuden yhteystiedon tietoina tai hakusanana. Jos käyttäjän syöte on `add Maija Meikäläinen, maija@example.com, +35850555556`, tulee muuttujien arvoiksi seuraavat: | command | theRest | |---------|----------------------------------------------------| | add | Maija Meikäläinen, maija@example.com, +35850555556 | `theRest`-muuttuja siis sisältää rivin koko loppuosan, joka `add`-komennon tapauksessa kannattaa pilkkoa String-luokan `split`-metodilla pilkkujen kohdalta: ``` String[] parts = theRest.split(","); String name = parts[0].trim(); String email = parts[1].trim(); String phone = parts[2].trim(); ``` ## Ohjelman logiikan haarauttaminen Syötteen lukemisen jälkeen voidaan koodi haarauttaa joko Javan perusteista tutulla `if-else if-else`-ketjulla tai `switch case`-rakenteella: ``` switch (command) { case "help": // tulosta ohje break; case "list": // tulosta osoitekirjan koko sisältö break; case "add": // käytä annettua nimeä, emailia ja puhelinnumeroa luodaksesi uuden yhteystiedon break; case "search": // etsi yhteystietoa ja tulosta se break; case "exit": // poistu ohjelmasta break; default: // tunnistamaton komento: System.out.println("Unknown command"); break; } ``` `Switch-case` -rakenne ei ole tässä välttämätön, mutta se sopii tilanteeseen hyvin ja sen opettelu voi olla hyödyksi sinulle myös myöhemmin. Koska ohjelman halutaan toimivan toistaiseksi ja kysyvän käyttäjän syötettä aina uudelleen, on edellä esitetyt syötteen kyselyt ja ehtorakenteet toteutettava jonkin toistorakenteen sisään. ## Käyttöliittymän ominaisuudet Sinun ei tarvitse toteuttaa kaikkia komentoja ohjelman ensimmäiseen versioon, vaan **toteuta ominaisuudet yksittäin sitä mukaan kun tarvitset niitä.** ### ADD add-komennon tulee lisätä yhdelle riville lisätyt yhteystiedot uutena oliona `AddressBook`-osoitekirjaan: ``` > add Maija Meikäläinen, maija@example.com, +35850555556 Added Maija Meikäläinen (email: maija@example.com, phone: +35850555556) ``` ### SEARCH Jos annettu hakusana löytyy jostain osoitelistan yhteystiedosta, ohjelman tulee tulostaa kyseinen yhteystieto. Jos yhteystietoa ei löydy, tulostetaan siitä kertova viesti: ``` > search Maija Maija Meikäläinen (email: maija@example.com, phone: +35850555556) > search Ville Ville does not match any contact. ``` ### LIST Komennon `list` jälkeen ohjelman tulee tulostaa kukin yhteystieto omalla rivillään: ``` > list Maija Meikäläinen (email: maija@example.com, phone: +35850555556) Matti Meikäläinen (email: matti@example.com, phone: +35850555555) ``` ### HELP Käyttäjän syötettyä komennon `help` ohjelman tulee tulostaa sama ohjeteksti kuin ohjelman käynnistyksessä: ``` This is an address book application. Available commands: list help add , , search exit ``` ### EXIT Käyttäjän kirjoittaessa syötteeksi "exit" ohjelman tulee tulostaa teksti "Bye!" ja lopettaa suorituksensa: ``` > exit Bye! ``` ### Esimerkki ``` This is an address book application. Available commands: list help add , , search exit > add Maija Meikäläinen, maija@example.com, +35850555556 Added Maija Meikäläinen (email: maija@example.com, phone: +35850555556) > add Matti Meikäläinen, matti@example.com, +35850555555 Added Matti Meikäläinen (email: matti@example.com, phone: +35850555555) > search Matti Matti Meikäläinen (email: matti@example.com, phone: +35850555555) > search Maija Maija Meikäläinen (email: maija@example.com, phone: +35850555556) > list Maija Meikäläinen (email: maija@example.com, phone: +35850555556) Matti Meikäläinen (email: matti@example.com, phone: +35850555555) > search Ville Ville does not match any contact. > exit Bye! ``` **AddressBookApp.java ``` import java.util.ArrayList; import java.util.List; import java.util.Scanner; public class AddressBookApp { private static String welcomeText = "This is an address book application. Available commands:\n"; private static String commandMenu = " list\n" + " help\n" + " add , , \n" + " search \n" + " exit\n"; // Keep here instead of translating to a local variable private static String inputCLIPrefix = "> "; // Keep added addressBook values during application runtime private static AddressBook addressBook = new AddressBook(); public static void main(String[] args) { System.out.print(welcomeText + commandMenu); while (true) { runApp(); } } private static void runApp() { Scanner inputPrompt = new Scanner(System.in); Object[] getInput = parseInput(inputCLIPrefix, inputPrompt); String inputCmd = String.valueOf(getInput[0]); // TODO Avoid risk of ClassCastException List inputArgs = (ArrayList)getInput[1]; int argsCount = 3; switch(inputCmd) { case "list": System.out.println(addressBook.toString()); break; case "help": System.out.print(welcomeText + commandMenu); break; case "add": // TODO Should this be checked in AddressBook.add method instead? if (inputArgs.size() != argsCount) { System.err.printf("Invalid count of input arguments (expected %d).\n", argsCount); break; } String name = String.valueOf(inputArgs.get(0)); String email = String.valueOf(inputArgs.get(1)); String phone = String.valueOf(inputArgs.get(2)); Contact contact = new Contact(name, email, phone); // NOTE: Else condition not needed as 3 arguments is always passed // to this object method call if(addressBook.add(contact)) { System.out.printf("Added %s\n", contact.toString()); } break; case "search": String searchTerm = String.valueOf(inputArgs.get(0)); Contact match = addressBook.search(searchTerm); if (match == null) { System.out.printf("%s does not match any contact.\n", searchTerm); } else { System.out.println(match.toString()); } break; case "exit": System.out.print("Bye!\n"); System.exit(0); default: System.err.println("Command not found."); //break; } } private static Object[] parseInput(String prefix, Scanner inputRaw) { System.out.print(prefix); String inputR = inputRaw.nextLine(); String command = inputR.split(" ")[0]; String[] theRest = inputR.split(","); theRest[0] = theRest[0].replaceAll("^\\s*" + command + "\\s*",""); List arguments = new ArrayList(); for (int i = 0; i < theRest.length; i++) { arguments.add(theRest[i].trim()); } Object[] parsedOutput = new Object[2]; parsedOutput[0] = command; parsedOutput[1] = arguments; return parsedOutput; } } ``` **AddressBook.java** ``` import java.util.ArrayList; import java.util.List; public class AddressBook { private List contacts; public AddressBook() { this.contacts = new ArrayList<>(); } public boolean add(Contact newContact) { this.contacts.add(newContact); return true; } public Contact search(String keyword) { for (Contact current : this.contacts) { String name = current.getName(); if (name != null && name.toLowerCase().contains(keyword.toLowerCase())) { return current; // palautetaan löytynyt arvo heti } } return null; // palautetaan null, jos ei löytynyt } @Override public String toString() { String returnString = ""; for (Contact contact : this.contacts) { returnString += contact + "\n"; } return returnString; } } ``` **Contact.java** ``` public class Contact { private String name; private String email; private String phone; // NOTE: This does not validate or check input String values // (Is name actually a name, email a valid email, and phone number an actual phone number?) public Contact(String name, String email, String phone) { this.name = name; this.email = email; this.phone = phone; } public String getName() { return this.name; } public String toString() { // Haluttu muoto: "Maija Meikäläinen (email: foo@bar.fi, phone: 5555)" return this.name + " (email: " + this.email + ", phone: " + this.phone + ")"; } } ```