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.io.IOException;
20 import java.sql.SQLException;
21 import java.text.MessageFormat;
22 import java.util.Arrays;
23 import java.util.Scanner;
24 import org.mxchange.addressbook.application.AddressbookApplication;
25 import org.mxchange.addressbook.client.AddressbookClient;
26 import org.mxchange.addressbook.client.BaseAddressbookClient;
27 import org.mxchange.addressbook.contact.user.UserContact;
28 import org.mxchange.addressbook.exceptions.ContactAlreadyAddedException;
29 import org.mxchange.addressbook.manager.contact.ManageableAddressbookContact;
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.contact.Contact;
37 import org.mxchange.jcore.contact.Gender;
38 import org.mxchange.jcore.exceptions.BadTokenException;
39 import org.mxchange.jcore.exceptions.UnhandledUserChoiceException;
40 import org.mxchange.jcore.exceptions.UnsupportedDatabaseBackendException;
43 * A client for the console
45 * @author Roland Haeder
47 public class ConsoleClient extends BaseAddressbookClient implements AddressbookClient {
50 * Scanner instance for reading data from console input
52 private final Scanner scanner;
55 * Parameterless constructor
57 * @param application An instance of an Application class
59 public ConsoleClient (final Application application) {
61 this.getLogger().trace(MessageFormat.format("application={0} - CALLED!", application)); //NOI18N
63 // Set application instance
64 this.setApplication(application);
66 // Init scanner instance
67 this.scanner = new Scanner(System.in, "UTF-8"); //NOI18N
70 this.getLogger().trace("EXIT!"); //NOI18N
74 * Displays a textual address "box" of given contact
76 * @param contact Contact to show address for
79 public void displayAddressBox (final Contact contact) {
81 this.getLogger().trace(MessageFormat.format("contact={0} - CALLED!", contact)); //NOI18N
84 if (contact == null) {
86 throw new NullPointerException("contact is null"); //NOI18N
90 this.outputMessage(MessageFormat.format("Strasse, PLZ Ort, Land: {0}\n{1} {2}\n{3}", contact.getStreet(), contact.getZipCode(), contact.getCity(), contact.getCountryCode()));
93 this.getLogger().trace("EXIT!"); //NOI18N
97 * Displays a textual name "box" of given contact
99 * @param contact Contact to show name for
102 public void displayNameBox (final Contact contact) {
104 this.getLogger().trace(MessageFormat.format("contact={0} - CALLED!", contact)); //NOI18N
107 if (contact == null) {
109 throw new NullPointerException("contact is null"); //NOI18N
112 // Get translated gender as the user may want to see "Mr.", "Mrs."
113 String gender = contact.getTranslatedGender();
116 String companyName = contact.getCompanyName();
118 // If it is empty/null, then assume private contact
119 if ((companyName == null) || (companyName.isEmpty())) {
120 // Now put all together: gender, surname, family name
122 this.outputMessage(MessageFormat.format("Anrede, Vorname, Name: {0} {1} {2}", gender, contact.getSurname(), contact.getFamilyName()));
125 this.outputMessage(MessageFormat.format("Firma: {0}\nAnsprechpartner: {1} {2} {3}", companyName, gender, contact.getSurname(), contact.getFamilyName()));
129 this.getLogger().trace("EXIT!"); //NOI18N
133 * Displays a textual other data "box" of given contact
135 * @param contact Contact to show other data for
138 public void displayOtherDataBox (final Contact contact) {
140 this.getLogger().trace(MessageFormat.format("contact={0} - CALLED!", contact)); //NOI18N
143 if (contact == null) {
145 throw new NullPointerException("contact is null"); //NOI18N
148 // Cellphone and such ...
149 this.outputMessage(MessageFormat.format("Telefonnumer: {0}\nFaxnummer: {1}\nHandy: {2}\nKommentar:\n{3}", contact.getPhoneNumber(), contact.getFaxNumber(), contact.getCellphoneNumber(), contact.getComment()));
152 this.getLogger().trace("EXIT!"); //NOI18N
156 public void doChangeOwnAddressData (final Contact contact) {
158 this.getLogger().trace(MessageFormat.format("contact={0} - CALLED!", contact)); //NOI18N
161 if (contact == null) {
163 throw new NullPointerException("contact is null"); //NOI18N
166 // Make sure it is own contact
167 if (!contact.isOwnContact()) {
169 throw new IllegalArgumentException("Contact is not own data."); //NOI18N
172 // Get manager and cast it
173 ManageableAddressbookContact manager = (ManageableAddressbookContact) this.getManager();
175 // Own street and number
176 String streetNumber = manager.enterOwnStreet();
179 Long zipCode = (long) manager.enterOwnZipCode();
182 String city = manager.enterOwnCity();
185 String countryCode = manager.enterOwnCountryCode();
187 // Update address data
188 contact.setStreet(streetNumber);
189 contact.setZipCode(zipCode);
190 contact.setCity(city);
191 contact.setCountryCode(countryCode);
194 this.getLogger().trace("EXIT!"); //NOI18N
198 public void doChangeOwnNameData (final Contact contact) {
200 this.getLogger().trace(MessageFormat.format("contact={0} - CALLED!", contact)); //NOI18N
203 if (contact == null) {
205 throw new NullPointerException("contact is null"); //NOI18N
208 // Make sure it is own contact
209 if (!contact.isOwnContact()) {
211 throw new IllegalArgumentException("Contact is not own data."); //NOI18N
214 // Get manager and cast it
215 ManageableAddressbookContact manager = (ManageableAddressbookContact) this.getManager();
218 Gender gender = manager.enterOwnGender();
221 String surname = manager.enterOwnSurname();
224 String familyName = manager.enterOwnFamilyName();
227 String companyName = manager.enterOwnCompanyName();
229 // Update contact instance
230 contact.setGender(gender);
231 contact.setSurname(surname);
232 contact.setFamilyName(familyName);
233 contact.setCompanyName(companyName);
236 this.getLogger().trace("EXIT!"); //NOI18N
240 public void doChangeOwnOtherData (final Contact contact) {
242 this.getLogger().trace(MessageFormat.format("contact={0} - CALLED!", contact)); //NOI18N
245 if (contact == null) {
247 throw new NullPointerException("contact is null"); //NOI18N
250 // Make sure it is own contact
251 if (!contact.isOwnContact()) {
253 throw new IllegalArgumentException("Contact is not own data."); //NOI18N
256 // Get manager and cast it
257 ManageableAddressbookContact manager = (ManageableAddressbookContact) this.getManager();
260 String phoneNumber = manager.enterOwnPhoneNumber();
263 String cellphonePhoneNumber = manager.enterOwnCellNumber();
266 String faxNumber = manager.enterOwnFaxNumber();
269 String email = manager.enterOwnEmailAddress();
272 String comment = manager.enterOwnComment();
274 // Update contact instance
275 contact.setPhoneNumber(phoneNumber);
276 contact.setCellphoneNumber(cellphonePhoneNumber);
277 contact.setFaxNumber(faxNumber);
278 contact.setEmailAddress(email);
279 contact.setComment(comment);
282 this.getLogger().trace("EXIT!"); //NOI18N
286 public Contact doEnterOwnData () {
288 this.getLogger().trace("CALLED!"); //NOI18N
290 // Get manager and cast it
291 ManageableAddressbookContact manager = (ManageableAddressbookContact) this.getManager();
293 // First ask for gender
294 Gender gender = manager.enterOwnGender();
297 String surname = manager.enterOwnSurname();
299 // And 3rd for family name
300 String familyName = manager.enterOwnFamilyName();
303 String companyName = manager.enterOwnCompanyName();
305 // Construct UserContact instance
306 Contact contact = new UserContact(gender, surname, familyName, companyName);
309 this.getLogger().trace(MessageFormat.format("contact={0} - EXIT!", contact)); //NOI18N
316 * Shutdown this client
319 public void doShutdown () throws SQLException, IOException {
321 this.getLogger().trace("CALLED!"); //NOI18N
326 // @TODO Add other shutdown stuff
329 this.getLogger().trace("EXIT!"); //NOI18N
333 public void doUserMenuChoice () throws UnhandledUserChoiceException {
335 this.getLogger().trace("CALLED!"); //NOI18N
337 // Get all access keys from menu
338 char[] accessKeys = MenuTools.getAccessKeysFromMenuMap(this.getMenus(), this.getCurrentMenu());
340 // Output textural message and ask for a char as input
341 char choice = this.enterChar(accessKeys, "Bitte Auswahl eingeben (0=Programm beenden): ");
343 // Get manager and cast it
344 ManageableAddressbookContact manager = (ManageableAddressbookContact) this.getManager();
348 // @TODO Rewrite this ugly switch() block
352 // Enter/add own data
353 manager.doEnterOwnData();
354 } catch (final ContactAlreadyAddedException ex) {
356 this.outputMessage("Sie haben bereits Ihre eigenen Daten eingegeben.");
360 case '2': // Change own data
361 manager.doChangeOwnData();
364 case '3': // Add new addess
365 manager.doAddOtherAddress();
368 case '4': // List contacts
369 manager.doListContacts();
372 case '5': // Search addresses
373 manager.doSearchContacts();
376 case '6': // Change other addess
377 manager.doChangeOtherAddress();
380 case '7': // Delete other address
381 manager.doDeleteOtherAddress();
387 this.getApplication().doShutdown();
388 } catch (final SQLException | IOException ex) {
389 this.abortProgramWithException(ex);
395 // @TODO throw own exception
396 throw new UnhandledUserChoiceException(MessageFormat.format("Choice '{0}' not handled yet.", choice)); //NOI18N
398 } catch (final IOException | BadTokenException ex) {
399 // Something bad happened
400 this.abortProgramWithException(ex);
404 this.getLogger().trace("EXIT!"); //NOI18N
408 * Asks the the user to enter a single character which must match validChars
410 * @param validChars Valid chars that are accepted
411 * @param message Message to user
412 * @return Allowed character
415 public char enterChar (final char[] validChars, final String message) {
417 this.getLogger().trace(MessageFormat.format("validChars={0},message={1} - CALLED!", Arrays.toString(validChars), message)); //NOI18N
419 // The validChars must not null be null and filled with at least one char
420 if (validChars == null) {
422 throw new NullPointerException("validChars is null"); //NOI18N
423 } else if (validChars.length == 0) {
425 throw new IllegalArgumentException("validChars is not filled."); //NOI18N
430 // Sort array, else binarySearch() won't work
431 Arrays.sort(validChars);
433 // Keep asking until valid char has been entered
434 while (Arrays.binarySearch(validChars, input) < 0) {
436 System.out.print(message);
439 input = this.readChar();
443 this.getLogger().trace(MessageFormat.format("input={0} - EXIT!", input)); //NOI18N
450 * Asks the user to enter his/her gender
452 * @param message Message to the user
453 * @return Gender enum
456 public Gender enterGender (final String message) {
458 this.getLogger().trace(MessageFormat.format("message={0} - CALLED!", message)); //NOI18N
461 char[] validChars = Gender.validChars();
464 //* NOISY-DEBUG: */ System.out.println(validChars);
466 char gender = this.enterChar(validChars, message);
468 // Now get a Gender instance back
469 Gender g = Gender.fromChar(gender);
471 // g must not be null
472 assert(g instanceof Gender) : "g is not set."; //NOI18N
475 this.getLogger().trace(MessageFormat.format("g={0} - EXIT!", g)); //NOI18N
482 * Reads an integer (int) with a textural message from the user
484 * @param minimum Minimum allowed number
485 * @param maximum Maximum allowed number
486 * @param message Messager to display in console
490 public int enterInt (final int minimum, final int maximum, final String message) {
492 this.getLogger().trace(MessageFormat.format("minimum={0},maximum={1},message={2} - CALLED!", minimum, maximum, message)); //NOI18N
494 // Minimum should not be below zero
495 assert (minimum >= 0);
496 assert (maximum > minimum);
501 while ((input < minimum) || (input > maximum)) {
503 System.out.print(message);
505 // Read integer from user
506 input = this.readInt();
510 this.getLogger().trace(MessageFormat.format("input={0} - EXIT!", input)); //NOI18N
517 * Reads a string of minimum and maximum length from the user
519 * @param minLength Minimum length of the string to read
520 * @param maxLength Maximum length of the string to read
521 * @param message Message to user
522 * @param allowEmpty Whether to allow empty string
523 * @return Entered string by user or null for empty strings
526 public String enterString (final int minLength, final int maxLength, final String message, final boolean allowEmpty) {
528 this.getLogger().trace(MessageFormat.format("minLength={0},maxLength={1},message={2}allowEmpty={3} - CALLED!", minLength, maxLength, message, allowEmpty)); //NOI18N
530 // Check on length, e.g. country codes are excactly 2 chars long
531 assert (maxLength >= minLength);
536 // Check if it is to short or to long
537 while (((input == null) || ((input.length() < minLength) && (!allowEmpty))) || ((input.length() > 0) && (input.length() < minLength) && (allowEmpty)) || ((input instanceof String) && (input.length() > maxLength))) {
539 System.out.print(message);
542 input = this.readString();
546 this.getLogger().trace(MessageFormat.format("input={0} - EXIT!", input)); //NOI18N
553 * Returns a console menu item
555 * @param accessKey Key to access the menu
556 * @param text Text to show to user
557 * @return A SelectableMenuItem
558 * @todo Make sure the access key is unique
561 public SelectableMenuItem getMenuItem (final char accessKey, final String text) {
562 // Return a new console menu item
563 return new ConsoleMenuItem(accessKey, text);
567 * Initializes this client
570 public void init () {
572 this.getLogger().trace("CALLED!"); //NOI18N
574 // Init contact manager here
576 this.initContactManager();
577 } catch (final UnsupportedDatabaseBackendException | SQLException ex) {
579 this.abortProgramWithException(ex);
586 this.getLogger().trace("EXIT!"); //NOI18N
590 * Displays textural message to the user
595 public void outputMessage (final String message) {
596 System.out.println(message);
600 * Shows textural menu on console
603 public void showCurrentMenu () {
604 this.showMenu(this.getCurrentMenu());
608 * Shows given menu entry to user
610 * @param item Menu entry
613 public void showEntry (final SelectableMenuItem item) {
614 // Access key then text
615 this.outputMessage(MessageFormat.format("[{0}] {1}", item.getAccessKey(), item.getText()));
619 * Shows a textural message to the user
622 public void showWelcome () {
623 this.outputMessage(MessageFormat.format("Welcome to {0}", AddressbookApplication.printableTitle()));
624 this.outputMessage("");
625 this.outputMessage("Copyright(c) 2015 by Roland Haeder, this is free software");
628 this.getLogger().debug("Intro shown to user"); //NOI18N
632 public void userChooseChangeContactData (final Contact contact) throws UnhandledUserChoiceException {
634 this.getLogger().trace(MessageFormat.format("contact={0} CALLED!", contact)); //NOI18N
636 // Contact must not be null
637 if (contact == null) {
639 throw new NullPointerException("contact is null"); //NOI18N
642 // Ask the user for editing [name], [a]ddress or [other] data
643 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) ");
645 // Get manager and cast it
646 ManageableAddressbookContact manager = (ManageableAddressbookContact) this.getManager();
648 // @TODO Get rid of this ugly switch block, too
650 case 'n': // Name data
651 manager.doChangeNameData(contact);
654 case 'a': // Address data
655 manager.doChangeAddressData(contact);
658 case 'o': // Other data
659 manager.doChangeOtherData(contact);
662 case 'x': // Exit this menu
663 // Ignored as it should go back
667 // @TODO throw own exception
668 throw new UnhandledUserChoiceException(MessageFormat.format("Choice '{0}' not handled yet.", choice)); //NOI18N
672 this.getLogger().trace("EXIT!"); //NOI18N
676 * Reads one character
678 * @return A single character
680 private char readChar () {
682 String input = this.readString();
685 this.getLogger().debug(MessageFormat.format("input={0}", input)); //NOI18N
687 // This must be only one character
688 if (input.length() != 1) {
693 // Get char from first (and only) position
694 return input.charAt(0);
698 * Reads an integer (int) from user
700 * @return An integer number
702 private int readInt () {
703 // First read a string
704 String input = this.readString();
707 this.getLogger().debug(MessageFormat.format("input={0}", input)); //NOI18N
709 // Init number with invalid value
712 // Parse number, this can be risky
714 num = Integer.parseInt(input);
715 } catch (final NumberFormatException e) {
716 this.outputMessage("Bitte geben Sie nur Zahlen ein!");
717 this.getLogger().warn(MessageFormat.format("No numbers-only entered. input={0},message={1}", input, e.getMessage())); //NOI18N
721 this.getLogger().trace(MessageFormat.format("num={0} - EXIT!", num)); //NOI18N
723 // Return read number
728 * Reads a string from a scanner until RETURN is pressed
730 * @return Read string from scanner
732 private String readString () {
733 return this.scanner.nextLine();
737 * Fills menu map with menu entries
740 protected final void fillMenuMap () {
742 this.getLogger().trace("CALLED!"); //NOI18N
744 // Initialize first (main) menu
745 Menu menu = new ConsoleMenu("main", this); //NOI18N
748 this.getMenus().put("main", menu); //NOI18N
751 this.getLogger().trace("EXIT!"); //NOI18N