2 * Copyright (C) 2015 Roland Haeder
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 package org.mxchange.addressbook.client.console;
19 import java.text.MessageFormat;
20 import java.util.Arrays;
21 import java.util.Scanner;
22 import org.mxchange.addressbook.application.AddressbookApplication;
23 import org.mxchange.addressbook.client.AddressbookClient;
24 import org.mxchange.addressbook.client.BaseAddressbookClient;
25 import org.mxchange.jcore.contact.Contact;
26 import org.mxchange.jcore.contact.Gender;
27 import org.mxchange.addressbook.contact.user.UserContact;
28 import org.mxchange.addressbook.exceptions.ContactAlreadyAddedException;
29 import org.mxchange.addressbook.manager.contact.ManageableContact;
30 import org.mxchange.addressbook.menu.Menu;
31 import org.mxchange.addressbook.menu.MenuTools;
32 import org.mxchange.addressbook.menu.console.ConsoleMenu;
33 import org.mxchange.addressbook.menu.item.SelectableMenuItem;
34 import org.mxchange.addressbook.menu.item.console.ConsoleMenuItem;
35 import org.mxchange.jcore.application.Application;
36 import org.mxchange.jcore.exceptions.UnhandledUserChoiceException;
39 * A client for the console
41 * @author Roland Haeder
43 public class ConsoleClient extends BaseAddressbookClient implements AddressbookClient {
46 * Scanner instance for reading data from console input
48 private final Scanner scanner;
51 * Parameterless constructor
53 * @param application An instance of an Application class
55 public ConsoleClient (final Application application) {
57 this.getLogger().trace(MessageFormat.format("application={0} - CALLED!", application)); //NOI18N
59 // Set application instance
60 this.setApplication(application);
62 // Init scanner instance
63 this.scanner = new Scanner(System.in, "UTF-8"); //NOI18N
66 this.getLogger().trace("EXIT!"); //NOI18N
70 * Displays a textual address "box" of given contact
72 * @param contact Contact to show address for
75 public void displayAddressBox (final Contact contact) {
77 this.getLogger().trace(MessageFormat.format("contact={0} - CALLED!", contact)); //NOI18N
80 if (contact == null) {
82 throw new NullPointerException("contact is null"); //NOI18N
86 this.outputMessage(MessageFormat.format("Strasse, PLZ Ort, Land: {0}\n{1} {2}\n{3}", contact.getStreet(), contact.getZipCode(), contact.getCity(), contact.getCountryCode()));
89 this.getLogger().trace("EXIT!"); //NOI18N
93 * Displays a textual name "box" of given contact
95 * @param contact Contact to show name for
98 public void displayNameBox (final Contact contact) {
100 this.getLogger().trace(MessageFormat.format("contact={0} - CALLED!", contact)); //NOI18N
103 if (contact == null) {
105 throw new NullPointerException("contact is null"); //NOI18N
108 // Get translated gender as the user may want to see "Mr.", "Mrs."
109 String gender = contact.getTranslatedGender();
112 String companyName = contact.getCompanyName();
114 // If it is empty/null, then assume private contact
115 if ((companyName == null) || (companyName.isEmpty())) {
116 // Now put all together: gender, surname, family name
118 this.outputMessage(MessageFormat.format("Anrede, Vorname, Name: {0} {1} {2}", gender, contact.getSurname(), contact.getFamilyName()));
121 this.outputMessage(MessageFormat.format("Firma: {0}\nAnsprechpartner: {1} {2} {3}", companyName, gender, contact.getSurname(), contact.getFamilyName()));
125 this.getLogger().trace("EXIT!"); //NOI18N
129 * Displays a textual other data "box" of given contact
131 * @param contact Contact to show other data for
134 public void displayOtherDataBox (final Contact contact) {
136 this.getLogger().trace(MessageFormat.format("contact={0} - CALLED!", contact)); //NOI18N
139 if (contact == null) {
141 throw new NullPointerException("contact is null"); //NOI18N
144 // Cellphone and such ...
145 this.outputMessage(MessageFormat.format("Telefonnumer: {0}\nFaxnummer: {1}\nHandy: {2}\nKommentar:\n{3}", contact.getPhoneNumber(), contact.getFaxNumber(), contact.getCellphoneNumber(), contact.getComment()));
148 this.getLogger().trace("EXIT!"); //NOI18N
152 public void doChangeOwnAddressData (final Contact contact) {
154 this.getLogger().trace(MessageFormat.format("contact={0} - CALLED!", contact)); //NOI18N
157 if (contact == null) {
159 throw new NullPointerException("contact is null"); //NOI18N
162 // Make sure it is own contact
163 if (!contact.isOwnContact()) {
165 throw new IllegalArgumentException("Contact is not own data."); //NOI18N
168 // Get manager and cast it
169 ManageableContact manager = (ManageableContact) this.getManager();
172 String street = manager.enterOwnStreet();
175 int zipCode = manager.enterOwnZipCode();
178 String city = manager.enterOwnCity();
181 String countryCode = manager.enterOwnCountryCode();
183 // Update address data
184 contact.updateAddressData(street, zipCode, city, countryCode);
187 this.getLogger().trace("EXIT!"); //NOI18N
191 public void doChangeOwnNameData (final Contact contact) {
193 this.getLogger().trace(MessageFormat.format("contact={0} - CALLED!", contact)); //NOI18N
196 if (contact == null) {
198 throw new NullPointerException("contact is null"); //NOI18N
201 // Make sure it is own contact
202 if (!contact.isOwnContact()) {
204 throw new IllegalArgumentException("Contact is not own data."); //NOI18N
207 // Get manager and cast it
208 ManageableContact manager = (ManageableContact) this.getManager();
211 Gender gender = manager.enterOwnGender();
214 String surname = manager.enterOwnSurname();
217 String familyName = manager.enterOwnFamilyName();
220 String companyName = manager.enterOwnCompanyName();
222 // Update contact instance
223 contact.updateNameData(gender, surname, familyName, companyName);
226 this.getLogger().trace("EXIT!"); //NOI18N
230 public void doChangeOwnOtherData (final Contact contact) {
232 this.getLogger().trace(MessageFormat.format("contact={0} - CALLED!", contact)); //NOI18N
235 if (contact == null) {
237 throw new NullPointerException("contact is null"); //NOI18N
240 // Make sure it is own contact
241 if (!contact.isOwnContact()) {
243 throw new IllegalArgumentException("Contact is not own data."); //NOI18N
246 // Get manager and cast it
247 ManageableContact manager = (ManageableContact) this.getManager();
250 String phoneNumber = manager.enterOwnPhoneNumber();
253 String cellNumber = manager.enterOwnCellNumber();
256 String faxNumber = manager.enterOwnFaxNumber();
259 String email = manager.enterOwnEmailAddress();
262 String comment = manager.enterOwnComment();
264 // Update contact instance
265 contact.updateOtherData(phoneNumber, cellNumber, faxNumber, email, null, comment);
268 this.getLogger().trace("EXIT!"); //NOI18N
272 public Contact doEnterOwnData () {
274 this.getLogger().trace("CALLED!"); //NOI18N
276 // Get manager and cast it
277 ManageableContact manager = (ManageableContact) this.getManager();
279 // First ask for gender
280 Gender gender = manager.enterOwnGender();
283 String surname = manager.enterOwnSurname();
285 // And 3rd for family name
286 String familyName = manager.enterOwnFamilyName();
289 String companyName = manager.enterOwnCompanyName();
291 // Construct UserContact instance
292 Contact contact = new UserContact(gender, surname, familyName, companyName);
295 this.getLogger().trace(MessageFormat.format("contact={0} - EXIT!", contact)); //NOI18N
302 * Shutdown this client
305 public void doShutdown () {
307 this.getLogger().trace("CALLED!"); //NOI18N
312 // @TODO Add other shutdown stuff
315 this.getLogger().trace("EXIT!"); //NOI18N
319 public void doUserMenuChoice () throws UnhandledUserChoiceException {
321 this.getLogger().trace("CALLED!"); //NOI18N
323 // Get all access keys from menu
324 char[] accessKeys = MenuTools.getAccessKeysFromMenuMap(this.getMenus(), this.getCurrentMenu());
326 // Output textural message and ask for a char as input
327 char choice = this.enterChar(accessKeys, "Bitte Auswahl eingeben (0=Programm beenden): ");
329 // Get manager and cast it
330 ManageableContact manager = (ManageableContact) this.getManager();
332 // @TODO Rewrite this ugly switch() block
336 // Enter/add own data
337 manager.doEnterOwnData();
338 } catch (final ContactAlreadyAddedException ex) {
340 this.outputMessage("Sie haben bereits Ihre eigenen Daten eingegeben.");
344 case '2': // Change own data
345 manager.doChangeOwnData();
348 case '3': // Add new addess
349 manager.doAddOtherAddress();
352 case '4': // List contacts
353 manager.doListContacts();
356 case '5': // Search addresses
357 manager.doSearchContacts();
360 case '6': // Change other addess
361 manager.doChangeOtherAddress();
364 case '7': // Delete other address
365 manager.doDeleteOtherAddress();
368 case '0': // Program exit
369 this.getApplication().doShutdown();
373 // @TODO throw own exception
374 throw new UnhandledUserChoiceException(MessageFormat.format("Choice '{0}' not handled yet.", choice)); //NOI18N
378 this.getLogger().trace("EXIT!"); //NOI18N
382 * Asks the the user to enter a single character which must match validChars
384 * @param validChars Valid chars that are accepted
385 * @param message Message to user
386 * @return Allowed character
389 public char enterChar (final char[] validChars, final String message) {
391 this.getLogger().trace(MessageFormat.format("validChars={0},message={1} - CALLED!", Arrays.toString(validChars), message)); //NOI18N
393 // The validChars must not null be null and filled with at least one char
394 if (validChars == null) {
396 throw new NullPointerException("validChars is null"); //NOI18N
397 } else if (validChars.length == 0) {
399 throw new IllegalArgumentException("validChars is not filled."); //NOI18N
404 // Sort array, else binarySearch() won't work
405 Arrays.sort(validChars);
407 // Keep asking until valid char has been entered
408 while (Arrays.binarySearch(validChars, input) < 0) {
410 System.out.print(message);
413 input = this.readChar();
417 this.getLogger().trace(MessageFormat.format("input={0} - EXIT!", input)); //NOI18N
424 * Asks the user to enter his/her gender
426 * @param message Message to the user
427 * @return Gender enum
430 public Gender enterGender (final String message) {
432 this.getLogger().trace(MessageFormat.format("message={0} - CALLED!", message)); //NOI18N
435 char[] validChars = Gender.validChars();
438 //* NOISY-DEBUG: */ System.out.println(validChars);
440 char gender = this.enterChar(validChars, message);
442 // Now get a Gender instance back
443 Gender g = Gender.fromChar(gender);
445 // g must not be null
446 assert(g instanceof Gender) : "g is not set."; //NOI18N
449 this.getLogger().trace(MessageFormat.format("g={0} - EXIT!", g)); //NOI18N
456 * Reads an integer (int) with a textural message from the user
458 * @param minimum Minimum allowed number
459 * @param maximum Maximum allowed number
460 * @param message Messager to display in console
464 public int enterInt (final int minimum, final int maximum, final String message) {
466 this.getLogger().trace(MessageFormat.format("minimum={0},maximum={1},message={2} - CALLED!", minimum, maximum, message)); //NOI18N
468 // Minimum should not be below zero
469 assert (minimum >= 0);
470 assert (maximum > minimum);
475 while ((input < minimum) || (input > maximum)) {
477 System.out.print(message);
479 // Read integer from user
480 input = this.readInt();
484 this.getLogger().trace(MessageFormat.format("input={0} - EXIT!", input)); //NOI18N
491 * Reads a string of minimum and maximum length from the user
493 * @param minLength Minimum length of the string to read
494 * @param maxLength Maximum length of the string to read
495 * @param message Message to user
496 * @param allowEmpty Whether to allow empty string
497 * @return Entered string by user or null for empty strings
500 public String enterString (final int minLength, final int maxLength, final String message, final boolean allowEmpty) {
502 this.getLogger().trace(MessageFormat.format("minLength={0},maxLength={1},message={2}allowEmpty={3} - CALLED!", minLength, maxLength, message, allowEmpty)); //NOI18N
504 // Check on length, e.g. country codes are excactly 2 chars long
505 assert (maxLength >= minLength);
510 // Check if it is to short or to long
511 while (((input == null) || ((input.length() < minLength) && (!allowEmpty))) || ((input.length() > 0) && (input.length() < minLength) && (allowEmpty)) || ((input instanceof String) && (input.length() > maxLength))) {
513 System.out.print(message);
516 input = this.readString();
520 this.getLogger().trace(MessageFormat.format("input={0} - EXIT!", input)); //NOI18N
527 * Returns a console menu item
529 * @param accessKey Key to access the menu
530 * @param text Text to show to user
531 * @return A SelectableMenuItem
532 * @todo Make sure the access key is unique
535 public SelectableMenuItem getMenuItem (final char accessKey, final String text) {
536 // Return a new console menu item
537 return new ConsoleMenuItem(accessKey, text);
541 * Inizializes this client
544 public void init () {
546 this.getLogger().trace("CALLED!"); //NOI18N
548 // Init contact manager here
549 this.initContactManager();
555 this.getLogger().trace("EXIT!"); //NOI18N
559 * Displays textural message to the user
564 public void outputMessage (final String message) {
565 System.out.println(message);
569 * Shows textural menu on console
572 public void showCurrentMenu () {
573 this.showMenu(this.getCurrentMenu());
577 * Shows given menu entry to user
579 * @param item Menu entry
582 public void showEntry (final SelectableMenuItem item) {
583 // Access key then text
584 this.outputMessage(MessageFormat.format("[{0}] {1}", item.getAccessKey(), item.getText()));
588 * Shows a textural message to the user
591 public void showWelcome () {
592 this.outputMessage(MessageFormat.format("Welcome to {0}", AddressbookApplication.printableTitle()));
593 this.outputMessage("");
594 this.outputMessage("Copyright(c) 2015 by Roland Haeder, this is free software");
597 this.getLogger().debug("Intro shown to user"); //NOI18N
601 public void userChooseChangeContactData (final Contact contact) throws UnhandledUserChoiceException {
603 this.getLogger().trace(MessageFormat.format("contact={0} CALLED!", contact)); //NOI18N
605 // Contact must not be null
606 if (contact == null) {
608 throw new NullPointerException("contact is null"); //NOI18N
611 // Ask the user for editing [name], [a]ddress or [other] data
612 char choice = this.enterChar(new char[] {'n', 'a', 'o', 'x'}, "Welchen Daten möchten Sie ändern? (n=Namensdaten, a=Anschriftsdaten, o=Andere, x=Zurück zur Hauptauswahl) ");
614 // Get manager and cast it
615 ManageableContact manager = (ManageableContact) this.getManager();
617 // @TODO Get rid of this ugly switch block, too
619 case 'n': // Name data
620 manager.doChangeNameData(contact);
623 case 'a': // Address data
624 manager.doChangeAddressData(contact);
627 case 'o': // Other data
628 manager.doChangeOtherData(contact);
631 case 'x': // Exit this menu
632 // Ignored as it should go back
636 // @TODO throw own exception
637 throw new UnhandledUserChoiceException(MessageFormat.format("Choice '{0}' not handled yet.", choice)); //NOI18N
641 this.getLogger().trace("EXIT!"); //NOI18N
645 * Reads one character
647 * @return A single character
649 private char readChar () {
651 String input = this.readString();
654 this.getLogger().debug(MessageFormat.format("input={0}", input)); //NOI18N
656 // This must be only one character
657 if (input.length() != 1) {
662 // Get char from first (and only) position
663 return input.charAt(0);
667 * Reads an integer (int) from user
669 * @return An integer number
671 private int readInt () {
672 // First read a string
673 String input = this.readString();
676 this.getLogger().debug(MessageFormat.format("input={0}", input)); //NOI18N
678 // Init number with invalid value
681 // Parse number, this can be risky
683 num = Integer.parseInt(input);
684 } catch (final NumberFormatException e) {
685 this.outputMessage("Bitte geben Sie nur Zahlen ein!");
686 this.getLogger().warn(MessageFormat.format("No numbers-only entered. input={0},message={1}", input, e.getMessage())); //NOI18N
690 this.getLogger().trace(MessageFormat.format("num={0} - EXIT!", num)); //NOI18N
692 // Return read number
697 * Reads a string from a scanner until RETURN is pressed
699 * @return Read string from scanner
701 private String readString () {
702 return this.scanner.nextLine();
706 * Fills menu map with menu entries
708 protected final void fillMenuMap () {
710 this.getLogger().trace("CALLED!"); //NOI18N
712 // Initialize first (main) menu
713 Menu menu = new ConsoleMenu("main", this); //NOI18N
716 this.getMenus().put("main", menu); //NOI18N
719 this.getLogger().trace("EXIT!"); //NOI18N