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.lang.reflect.InvocationTargetException;
21 import java.sql.SQLException;
22 import java.text.MessageFormat;
23 import java.util.Arrays;
24 import java.util.Scanner;
25 import org.mxchange.addressbook.application.AddressbookApplication;
26 import org.mxchange.addressbook.client.AddressbookClient;
27 import org.mxchange.addressbook.client.BaseAddressbookClient;
28 import org.mxchange.addressbook.contact.user.UserContact;
29 import org.mxchange.addressbook.exceptions.ContactAlreadyAddedException;
30 import org.mxchange.addressbook.manager.contact.ManageableAddressbookContact;
31 import org.mxchange.addressbook.menu.Menu;
32 import org.mxchange.addressbook.menu.MenuTools;
33 import org.mxchange.addressbook.menu.console.ConsoleMenu;
34 import org.mxchange.addressbook.menu.item.SelectableMenuItem;
35 import org.mxchange.addressbook.menu.item.console.ConsoleMenuItem;
36 import org.mxchange.jcore.application.Application;
37 import org.mxchange.jcore.contact.Contact;
38 import org.mxchange.jcore.contact.Gender;
39 import org.mxchange.jcore.exceptions.BadTokenException;
40 import org.mxchange.jcore.exceptions.CorruptedDatabaseFileException;
41 import org.mxchange.jcore.exceptions.UnhandledUserChoiceException;
42 import org.mxchange.jcore.exceptions.UnsupportedDatabaseBackendException;
45 * A client for the console
47 * @author Roland Haeder
49 public class ConsoleClient extends BaseAddressbookClient implements AddressbookClient {
52 * Scanner instance for reading data from console input
54 private final Scanner scanner;
57 * Parameterless constructor
59 * @param application An instance of an Application class
61 public ConsoleClient (final Application application) {
63 this.getLogger().trace(MessageFormat.format("application={0} - CALLED!", application)); //NOI18N
65 // Set application instance
66 this.setApplication(application);
68 // Init scanner instance
69 this.scanner = new Scanner(System.in, "UTF-8"); //NOI18N
72 this.getLogger().trace("EXIT!"); //NOI18N
76 * Displays a textual address "box" of given contact
78 * @param contact Contact to show address for
81 public void displayAddressBox (final Contact contact) {
83 this.getLogger().trace(MessageFormat.format("contact={0} - CALLED!", contact)); //NOI18N
86 if (null == contact) {
88 throw new NullPointerException("contact is null"); //NOI18N
92 this.outputMessage(MessageFormat.format("Strasse, PLZ Ort, Land: {0}\n{1} {2}\n{3}", contact.getStreet(), contact.getZipCode(), contact.getCity(), contact.getCountryCode()));
95 this.getLogger().trace("EXIT!"); //NOI18N
99 * Displays a textual name "box" of given contact
101 * @param contact Contact to show name for
104 public void displayNameBox (final Contact contact) {
106 this.getLogger().trace(MessageFormat.format("contact={0} - CALLED!", contact)); //NOI18N
109 if (null == contact) {
111 throw new NullPointerException("contact is null"); //NOI18N
114 // Get translated gender as the user may want to see "Mr.", "Mrs."
115 String gender = contact.getTranslatedGender();
118 String companyName = contact.getCompanyName();
120 // If it is empty/null, then assume private contact
121 if ((null == companyName) || (companyName.isEmpty())) {
122 // Now put all together: gender, surname, family name
124 this.outputMessage(MessageFormat.format("Anrede, Vorname, Name: {0} {1} {2}", gender, contact.getFirstName(), contact.getFamilyName()));
127 this.outputMessage(MessageFormat.format("Firma: {0}\nAnsprechpartner: {1} {2} {3}", companyName, gender, contact.getFirstName(), contact.getFamilyName()));
131 this.getLogger().trace("EXIT!"); //NOI18N
135 * Displays a textual other data "box" of given contact
137 * @param contact Contact to show other data for
140 public void displayOtherDataBox (final Contact contact) {
142 this.getLogger().trace(MessageFormat.format("contact={0} - CALLED!", contact)); //NOI18N
145 if (null == contact) {
147 throw new NullPointerException("contact is null"); //NOI18N
150 // Cellphone and such ...
151 this.outputMessage(MessageFormat.format("Telefonnumer: {0}\nFaxnummer: {1}\nHandy: {2}\nKommentar:\n{3}", contact.getPhoneNumber(), contact.getFaxNumber(), contact.getCellphoneNumber(), contact.getComment()));
154 this.getLogger().trace("EXIT!"); //NOI18N
158 public void doChangeOwnAddressData (final Contact contact) {
160 this.getLogger().trace(MessageFormat.format("contact={0} - CALLED!", contact)); //NOI18N
163 if (null == contact) {
165 throw new NullPointerException("contact is null"); //NOI18N
168 // Make sure it is own contact
169 if (!contact.isOwnContact()) {
171 throw new IllegalArgumentException("Contact is not own data."); //NOI18N
174 // Get manager and cast it
175 ManageableAddressbookContact manager = (ManageableAddressbookContact) this.getManager();
177 // Own street and number
178 String streetNumber = manager.enterOwnStreet();
181 Long zipCode = (long) manager.enterOwnZipCode();
184 String city = manager.enterOwnCity();
187 String countryCode = manager.enterOwnCountryCode();
189 // Update address data
190 contact.setStreet(streetNumber);
191 contact.setZipCode(zipCode);
192 contact.setCity(city);
193 contact.setCountryCode(countryCode);
196 this.getLogger().trace("EXIT!"); //NOI18N
200 public void doChangeOwnNameData (final Contact contact) {
202 this.getLogger().trace(MessageFormat.format("contact={0} - CALLED!", contact)); //NOI18N
205 if (null == contact) {
207 throw new NullPointerException("contact is null"); //NOI18N
210 // Make sure it is own contact
211 if (!contact.isOwnContact()) {
213 throw new IllegalArgumentException("Contact is not own data."); //NOI18N
216 // Get manager and cast it
217 ManageableAddressbookContact manager = (ManageableAddressbookContact) this.getManager();
220 Gender gender = manager.enterOwnGender();
223 String firstName = manager.enterOwnFirstName();
226 String familyName = manager.enterOwnFamilyName();
229 String companyName = manager.enterOwnCompanyName();
231 // Update contact instance
232 contact.setGender(gender);
233 contact.setFirstName(firstName);
234 contact.setFamilyName(familyName);
235 contact.setCompanyName(companyName);
238 this.getLogger().trace("EXIT!"); //NOI18N
242 public void doChangeOwnOtherData (final Contact contact) {
244 this.getLogger().trace(MessageFormat.format("contact={0} - CALLED!", contact)); //NOI18N
247 if (null == contact) {
249 throw new NullPointerException("contact is null"); //NOI18N
252 // Make sure it is own contact
253 if (!contact.isOwnContact()) {
255 throw new IllegalArgumentException("Contact is not own data."); //NOI18N
258 // Get manager and cast it
259 ManageableAddressbookContact manager = (ManageableAddressbookContact) this.getManager();
262 String phoneNumber = manager.enterOwnPhoneNumber();
265 String cellphonePhoneNumber = manager.enterOwnCellNumber();
268 String faxNumber = manager.enterOwnFaxNumber();
271 String email = manager.enterOwnEmailAddress();
274 String comment = manager.enterOwnComment();
276 // Update contact instance
277 contact.setPhoneNumber(phoneNumber);
278 contact.setCellphoneNumber(cellphonePhoneNumber);
279 contact.setFaxNumber(faxNumber);
280 contact.setEmailAddress(email);
281 contact.setComment(comment);
284 this.getLogger().trace("EXIT!"); //NOI18N
288 public Contact doEnterOwnData () {
290 this.getLogger().trace("CALLED!"); //NOI18N
292 // Get manager and cast it
293 ManageableAddressbookContact manager = (ManageableAddressbookContact) this.getManager();
295 // First ask for gender
296 Gender gender = manager.enterOwnGender();
299 String surname = manager.enterOwnFirstName();
301 // And 3rd for family name
302 String familyName = manager.enterOwnFamilyName();
305 String companyName = manager.enterOwnCompanyName();
307 // Construct UserContact instance
308 Contact contact = new UserContact(gender, surname, familyName, companyName);
311 this.getLogger().trace(MessageFormat.format("contact={0} - EXIT!", contact)); //NOI18N
318 * Shutdown this client
321 public void doShutdown () throws SQLException, IOException {
323 this.getLogger().trace("CALLED!"); //NOI18N
328 // TODO Add other shutdown stuff
331 this.getLogger().trace("EXIT!"); //NOI18N
335 public void doUserMenuChoice () throws UnhandledUserChoiceException {
337 this.getLogger().trace("CALLED!"); //NOI18N
339 // Get all access keys from menu
340 char[] accessKeys = MenuTools.getAccessKeysFromMenuMap(this.getMenus(), this.getCurrentMenu());
342 // Output textural message and ask for a char as input
343 char choice = this.enterChar(accessKeys, "Bitte Auswahl eingeben (0=Programm beenden): ");
345 // Get manager and cast it
346 ManageableAddressbookContact manager = (ManageableAddressbookContact) this.getManager();
350 // TODO Rewrite this ugly switch() block
354 // Enter/add own data
355 manager.doEnterOwnData();
356 } catch (final ContactAlreadyAddedException ex) {
358 this.outputMessage("Sie haben bereits Ihre eigenen Daten eingegeben.");
362 case '2': // Change own data
363 manager.doChangeOwnData();
366 case '3': // Add new addess
367 manager.doAddOtherAddress();
370 case '4': // List contacts
371 manager.doListContacts();
374 case '5': // Search addresses
375 manager.doSearchContacts();
378 case '6': // Change other addess
379 manager.doChangeOtherAddress();
382 case '7': // Delete other address
383 manager.doDeleteOtherAddress();
389 this.getApplication().doShutdown();
390 } catch (final SQLException | IOException ex) {
391 this.abortProgramWithException(ex);
396 // TODO throw own exception
397 throw new UnhandledUserChoiceException(MessageFormat.format("Choice '{0}' not handled yet.", choice)); //NOI18N
399 } catch (final IOException | BadTokenException | CorruptedDatabaseFileException | SQLException | NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
400 // Something bad happened
401 this.abortProgramWithException(ex);
405 this.getLogger().trace("EXIT!"); //NOI18N
409 * Asks the the user to enter a single character which must match validChars
411 * @param validChars Valid chars that are accepted
412 * @param message Message to user
413 * @return Allowed character
416 public char enterChar (final char[] validChars, final String message) {
418 this.getLogger().trace(MessageFormat.format("validChars={0},message={1} - CALLED!", Arrays.toString(validChars), message)); //NOI18N
420 // The validChars must not null be null and filled with at least one char
421 if (null == validChars) {
423 throw new NullPointerException("validChars is null"); //NOI18N
424 } else if (validChars.length == 0) {
426 throw new IllegalArgumentException("validChars is not filled."); //NOI18N
431 // Sort array, else binarySearch() won't work
432 Arrays.sort(validChars);
434 // Keep asking until valid char has been entered
435 while (Arrays.binarySearch(validChars, input) < 0) {
437 System.out.print(message);
440 input = this.readChar();
444 this.getLogger().trace(MessageFormat.format("input={0} - EXIT!", input)); //NOI18N
451 * Asks the user to enter his/her gender
453 * @param message Message to the user
454 * @return Gender enum
457 public Gender enterGender (final String message) {
459 this.getLogger().trace(MessageFormat.format("message={0} - CALLED!", message)); //NOI18N
462 char[] validChars = Gender.validChars();
465 //* NOISY-DEBUG: */ System.out.println(validChars);
467 char gender = this.enterChar(validChars, message);
469 // Now get a Gender instance back
470 Gender g = Gender.fromChar(gender);
472 // g must not be null
473 assert(g instanceof Gender) : "g is not set."; //NOI18N
476 this.getLogger().trace(MessageFormat.format("g={0} - EXIT!", g)); //NOI18N
483 * Reads an integer (int) with a textural message from the user
485 * @param minimum Minimum allowed number
486 * @param maximum Maximum allowed number
487 * @param message Messager to display in console
491 public int enterInt (final int minimum, final int maximum, final String message) {
493 this.getLogger().trace(MessageFormat.format("minimum={0},maximum={1},message={2} - CALLED!", minimum, maximum, message)); //NOI18N
495 // Minimum should not be below zero
496 assert (minimum >= 0);
497 assert (maximum > minimum);
502 while ((input < minimum) || (input > maximum)) {
504 System.out.print(message);
506 // Read integer from user
507 input = this.readInt();
511 this.getLogger().trace(MessageFormat.format("input={0} - EXIT!", input)); //NOI18N
518 * Reads a string of minimum and maximum length from the user
520 * @param minLength Minimum length of the string to read
521 * @param maxLength Maximum length of the string to read
522 * @param message Message to user
523 * @param allowEmpty Whether to allow empty string
524 * @return Entered string by user or null for empty strings
527 public String enterString (final int minLength, final int maxLength, final String message, final boolean allowEmpty) {
529 this.getLogger().trace(MessageFormat.format("minLength={0},maxLength={1},message={2}allowEmpty={3} - CALLED!", minLength, maxLength, message, allowEmpty)); //NOI18N
531 // Check on length, e.g. country codes are excactly 2 chars long
532 assert (maxLength >= minLength);
537 // Check if it is to short or to long
538 while (((null == input) || ((input.length() < minLength) && (!allowEmpty))) || ((input.length() > 0) && (input.length() < minLength) && (allowEmpty)) || ((input instanceof String) && (input.length() > maxLength))) {
540 System.out.print(message);
543 input = this.readString();
547 this.getLogger().trace(MessageFormat.format("input={0} - EXIT!", input)); //NOI18N
554 * Returns a console menu item
556 * @param accessKey Key to access the menu
557 * @param text Text to show to user
558 * @return A SelectableMenuItem
559 * TODO Make sure the access key is unique
562 public SelectableMenuItem getMenuItem (final char accessKey, final String text) {
563 // Return a new console menu item
564 return new ConsoleMenuItem(accessKey, text);
568 * Initializes this client
571 public void init () {
573 this.getLogger().trace("CALLED!"); //NOI18N
575 // Init contact manager here
577 this.initContactManager();
578 } catch (final UnsupportedDatabaseBackendException | SQLException ex) {
580 this.abortProgramWithException(ex);
587 this.getLogger().trace("EXIT!"); //NOI18N
591 * Displays textural message to the user
596 public void outputMessage (final String message) {
597 System.out.println(message);
601 * Shows textural menu on console
604 public void showCurrentMenu () {
605 this.showMenu(this.getCurrentMenu());
609 * Shows given menu entry to user
611 * @param item Menu entry
614 public void showEntry (final SelectableMenuItem item) {
615 // Access key then text
616 this.outputMessage(MessageFormat.format("[{0}] {1}", item.getAccessKey(), item.getText()));
620 * Shows a textural message to the user
623 public void showWelcome () {
624 this.outputMessage(MessageFormat.format("Welcome to {0}", AddressbookApplication.printableTitle()));
625 this.outputMessage("");
626 this.outputMessage("Copyright(c) 2015 by Roland Haeder, this is free software");
629 this.getLogger().debug("Intro shown to user"); //NOI18N
633 public void userChooseChangeContactData (final Contact contact) throws UnhandledUserChoiceException {
635 this.getLogger().trace(MessageFormat.format("contact={0} CALLED!", contact)); //NOI18N
637 // Contact must not be null
638 if (null == contact) {
640 throw new NullPointerException("contact is null"); //NOI18N
643 // Ask the user for editing [name], [a]ddress or [other] data
644 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) ");
646 // Get manager and cast it
647 ManageableAddressbookContact manager = (ManageableAddressbookContact) this.getManager();
649 // TODO Get rid of this ugly switch block, too
651 case 'n': // Name data
652 manager.doChangeNameData(contact);
655 case 'a': // Address data
656 manager.doChangeAddressData(contact);
659 case 'o': // Other data
660 manager.doChangeOtherData(contact);
663 case 'x': // Exit this menu
664 // Ignored as it should go back
668 // TODO throw own exception
669 throw new UnhandledUserChoiceException(MessageFormat.format("Choice '{0}' not handled yet.", choice)); //NOI18N
673 this.getLogger().trace("EXIT!"); //NOI18N
677 * Reads one character
679 * @return A single character
681 private char readChar () {
683 String input = this.readString();
686 this.getLogger().debug(MessageFormat.format("input={0}", input)); //NOI18N
688 // This must be only one character
689 if (input.length() != 1) {
694 // Get char from first (and only) position
695 return input.charAt(0);
699 * Reads an integer (int) from user
701 * @return An integer number
703 private int readInt () {
704 // First read a string
705 String input = this.readString();
708 this.getLogger().debug(MessageFormat.format("input={0}", input)); //NOI18N
710 // Init number with invalid value
713 // Parse number, this can be risky
715 num = Integer.parseInt(input);
716 } catch (final NumberFormatException e) {
717 this.outputMessage("Bitte geben Sie nur Zahlen ein!");
718 this.getLogger().warn(MessageFormat.format("No numbers-only entered. input={0},message={1}", input, e.getMessage())); //NOI18N
722 this.getLogger().trace(MessageFormat.format("num={0} - EXIT!", num)); //NOI18N
724 // Return read number
729 * Reads a string from a scanner until RETURN is pressed
731 * @return Read string from scanner
733 private String readString () {
734 return this.scanner.nextLine();
738 * Fills menu map with menu entries
741 protected final void fillMenuMap () {
743 this.getLogger().trace("CALLED!"); //NOI18N
745 // Initialize first (main) menu
746 Menu menu = new ConsoleMenu("main", this); //NOI18N
749 this.getMenus().put("main", menu); //NOI18N
752 this.getLogger().trace("EXIT!"); //NOI18N