]> git.mxchange.org Git - jaddressbook-share-lib.git/blob - src/org/mxchange/addressbook/client/console/ConsoleClient.java
6ed15fb2a04a79feee994d412fdfc53bbc388a1c
[jaddressbook-share-lib.git] / src / org / mxchange / addressbook / client / console / ConsoleClient.java
1 /*
2  * Copyright (C) 2015 Roland Haeder
3  *
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.
8  *
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.
13  *
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/>.
16  */
17 package org.mxchange.addressbook.client.console;
18
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;
41
42 /**
43  * A client for the console
44  *
45  * @author Roland Haeder
46  */
47 public class ConsoleClient extends BaseAddressbookClient implements AddressbookClient {
48
49         /**
50          * Scanner instance for reading data from console input
51          */
52         private final Scanner scanner;
53
54         /**
55          * Parameterless constructor
56          *
57          * @param application An instance of an Application class
58          */
59         public ConsoleClient (final Application application) {
60                 // Trace message
61                 this.getLogger().trace(MessageFormat.format("application={0} - CALLED!", application)); //NOI18N
62
63                 // Set application instance
64                 this.setApplication(application);
65
66                 // Init scanner instance
67                 this.scanner = new Scanner(System.in, "UTF-8"); //NOI18N
68
69                 // Trace message
70                 this.getLogger().trace("EXIT!"); //NOI18N
71         }
72
73         /**
74          * Displays a textual address "box" of given contact
75          *
76          * @param contact Contact to show address for
77          */
78         @Override
79         public void displayAddressBox (final Contact contact) {
80                 // Trace message
81                 this.getLogger().trace(MessageFormat.format("contact={0} - CALLED!", contact)); //NOI18N
82
83                 // Is it null?
84                 if (contact == null) {
85                         // Abort here
86                         throw new NullPointerException("contact is null"); //NOI18N
87                 }
88
89                 // Simple display ...
90                 this.outputMessage(MessageFormat.format("Strasse, PLZ Ort, Land: {0}\n{1} {2}\n{3}", contact.getStreet(), contact.getZipCode(), contact.getCity(), contact.getCountryCode()));
91
92                 // Trace message
93                 this.getLogger().trace("EXIT!"); //NOI18N
94         }
95
96         /**
97          * Displays a textual name "box" of given contact
98          *
99          * @param contact Contact to show name for
100          */
101         @Override
102         public void displayNameBox (final Contact contact) {
103                 // Trace message
104                 this.getLogger().trace(MessageFormat.format("contact={0} - CALLED!", contact)); //NOI18N
105
106                 // Is it null?
107                 if (contact == null) {
108                         // Abort here
109                         throw new NullPointerException("contact is null"); //NOI18N
110                 }
111
112                 // Get translated gender as the user may want to see "Mr.", "Mrs."
113                 String gender = contact.getTranslatedGender();
114
115                 // Get company name
116                 String companyName = contact.getCompanyName();
117
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
121                         // @todo Use mask
122                         this.outputMessage(MessageFormat.format("Anrede, Vorname, Name: {0} {1} {2}", gender, contact.getSurname(), contact.getFamilyName()));
123                 } else {
124                         // Company contact
125                         this.outputMessage(MessageFormat.format("Firma: {0}\nAnsprechpartner: {1} {2} {3}", companyName, gender, contact.getSurname(), contact.getFamilyName()));
126                 }
127
128                 // Trace message
129                 this.getLogger().trace("EXIT!"); //NOI18N
130         }
131
132         /**
133          * Displays a textual other data "box" of given contact
134          *
135          * @param contact Contact to show other data for
136          */
137         @Override
138         public void displayOtherDataBox (final Contact contact) {
139                 // Trace message
140                 this.getLogger().trace(MessageFormat.format("contact={0} - CALLED!", contact)); //NOI18N
141
142                 // Is it null?
143                 if (contact == null) {
144                         // Abort here
145                         throw new NullPointerException("contact is null"); //NOI18N
146                 }
147
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()));
150
151                 // Trace message
152                 this.getLogger().trace("EXIT!"); //NOI18N
153         }
154
155         @Override
156         public void doChangeOwnAddressData (final Contact contact) {
157                 // Trace message
158                 this.getLogger().trace(MessageFormat.format("contact={0} - CALLED!", contact)); //NOI18N
159
160                 // Is it null?
161                 if (contact == null) {
162                         // Abort here
163                         throw new NullPointerException("contact is null"); //NOI18N
164                 }
165
166                 // Make sure it is own contact
167                 if (!contact.isOwnContact()) {
168                         // Not own contact
169                         throw new IllegalArgumentException("Contact is not own data."); //NOI18N
170                 }
171
172                 // Get manager and cast it
173                 ManageableAddressbookContact manager = (ManageableAddressbookContact) this.getManager();
174
175                 // Own street and number
176                 String streetNumber = manager.enterOwnStreet();
177
178                 // Get zip code
179                 Long zipCode = (long) manager.enterOwnZipCode();
180
181                 // Get city name
182                 String city = manager.enterOwnCity();
183
184                 // Get country code
185                 String countryCode = manager.enterOwnCountryCode();
186
187                 // Update address data
188                 contact.setStreet(streetNumber);
189                 contact.setZipCode(zipCode);
190                 contact.setCity(city);
191                 contact.setCountryCode(countryCode);
192
193                 // Trace message
194                 this.getLogger().trace("EXIT!"); //NOI18N
195         }
196
197         @Override
198         public void doChangeOwnNameData (final Contact contact) {
199                 // Trace message
200                 this.getLogger().trace(MessageFormat.format("contact={0} - CALLED!", contact)); //NOI18N
201
202                 // Is it null?
203                 if (contact == null) {
204                         // Abort here
205                         throw new NullPointerException("contact is null"); //NOI18N
206                 }
207
208                 // Make sure it is own contact
209                 if (!contact.isOwnContact()) {
210                         // Not own contact
211                         throw new IllegalArgumentException("Contact is not own data."); //NOI18N
212                 }
213
214                 // Get manager and cast it
215                 ManageableAddressbookContact manager = (ManageableAddressbookContact) this.getManager();
216
217                 // Gender:
218                 Gender gender = manager.enterOwnGender();
219
220                 // Surname
221                 String surname = manager.enterOwnSurname();
222
223                 // Family name
224                 String familyName = manager.enterOwnFamilyName();
225
226                 // And company
227                 String companyName = manager.enterOwnCompanyName();
228
229                 // Update contact instance
230                 contact.setGender(gender);
231                 contact.setSurname(surname);
232                 contact.setFamilyName(familyName);
233                 contact.setCompanyName(companyName);
234
235                 // Trace message
236                 this.getLogger().trace("EXIT!"); //NOI18N
237         }
238
239         @Override
240         public void doChangeOwnOtherData (final Contact contact) {
241                 // Trace message
242                 this.getLogger().trace(MessageFormat.format("contact={0} - CALLED!", contact)); //NOI18N
243
244                 // Is it null?
245                 if (contact == null) {
246                         // Abort here
247                         throw new NullPointerException("contact is null"); //NOI18N
248                 }
249
250                 // Make sure it is own contact
251                 if (!contact.isOwnContact()) {
252                         // Not own contact
253                         throw new IllegalArgumentException("Contact is not own data."); //NOI18N
254                 }
255
256                 // Get manager and cast it
257                 ManageableAddressbookContact manager = (ManageableAddressbookContact) this.getManager();
258
259                 // Phone number
260                 String phoneNumber = manager.enterOwnPhoneNumber();
261
262                 // Phone number
263                 String cellphonePhoneNumber = manager.enterOwnCellNumber();
264
265                 // Fax number
266                 String faxNumber = manager.enterOwnFaxNumber();
267
268                 // Email address
269                 String email = manager.enterOwnEmailAddress();
270
271                 // Comment
272                 String comment = manager.enterOwnComment();
273
274                 // Update contact instance
275                 contact.setPhoneNumber(phoneNumber);
276                 contact.setCellphoneNumber(cellphonePhoneNumber);
277                 contact.setFaxNumber(faxNumber);
278                 contact.setEmailAddress(email);
279                 contact.setComment(comment);
280
281                 // Trace message
282                 this.getLogger().trace("EXIT!"); //NOI18N
283         }
284
285         @Override
286         public Contact doEnterOwnData () {
287                 // Trace message
288                 this.getLogger().trace("CALLED!"); //NOI18N
289
290                 // Get manager and cast it
291                 ManageableAddressbookContact manager = (ManageableAddressbookContact) this.getManager();
292
293                 // First ask for gender
294                 Gender gender = manager.enterOwnGender();
295
296                 // 2nd for surname
297                 String surname = manager.enterOwnSurname();
298
299                 // And 3rd for family name
300                 String familyName = manager.enterOwnFamilyName();
301
302                 // Company name ...
303                 String companyName = manager.enterOwnCompanyName();
304
305                 // Construct UserContact instance
306                 Contact contact = new UserContact(gender, surname, familyName, companyName);
307
308                 // Trace message
309                 this.getLogger().trace(MessageFormat.format("contact={0} - EXIT!", contact)); //NOI18N
310
311                 // And return object
312                 return contact;
313         }
314
315         /**
316          * Shutdown this client
317          */
318         @Override
319         public void doShutdown () throws SQLException, IOException {
320                 // Trace message
321                 this.getLogger().trace("CALLED!"); //NOI18N
322
323                 // Parent call
324                 super.doShutdown();
325
326                 // @TODO Add other shutdown stuff
327
328                 // Trace message
329                 this.getLogger().trace("EXIT!"); //NOI18N
330         }
331
332         @Override
333         public void doUserMenuChoice () throws UnhandledUserChoiceException {
334                 // Trace message
335                 this.getLogger().trace("CALLED!"); //NOI18N
336
337                 // Get all access keys from menu
338                 char[] accessKeys = MenuTools.getAccessKeysFromMenuMap(this.getMenus(), this.getCurrentMenu());
339
340                 // Output textural message and ask for a char as input
341                 char choice = this.enterChar(accessKeys, "Bitte Auswahl eingeben (0=Programm beenden): ");
342
343                 // Get manager and cast it
344                 ManageableAddressbookContact manager = (ManageableAddressbookContact) this.getManager();
345
346                 // Try it!
347                 try {
348                         // @TODO Rewrite this ugly switch() block
349                         switch (choice) {
350                                 case '1':
351                                         try {
352                                                 // Enter/add own data
353                                                 manager.doEnterOwnData();
354                                         } catch (final ContactAlreadyAddedException ex) {
355                                                 // Already added
356                                                 this.outputMessage("Sie haben bereits Ihre eigenen Daten eingegeben.");
357                                         }
358                                         break;
359
360                                 case '2': // Change own data
361                                         manager.doChangeOwnData();
362                                         break;
363
364                                 case '3': // Add new addess
365                                         manager.doAddOtherAddress();
366                                         break;
367
368                                 case '4': // List contacts
369                                         manager.doListContacts();
370                                         break;
371
372                                 case '5': // Search addresses
373                                         manager.doSearchContacts();
374                                         break;
375
376                                 case '6': // Change other addess
377                                         manager.doChangeOtherAddress();
378                                         break;
379
380                                 case '7': // Delete other address
381                                         manager.doDeleteOtherAddress();
382                                         break;
383
384                         case '0': {
385                         try {
386                                 // Program exit
387                                 this.getApplication().doShutdown();
388                         } catch (final SQLException | IOException ex) {
389                                 this.abortProgramWithException(ex);
390                         }
391                 }
392                                 break;
393
394                                 default:
395                                         // @TODO throw own exception
396                                         throw new UnhandledUserChoiceException(MessageFormat.format("Choice '{0}' not handled yet.", choice)); //NOI18N
397                         }
398                 } catch (final IOException | BadTokenException ex) {
399                         // Something bad happened
400                         this.abortProgramWithException(ex);
401                 }
402
403                 // Trace message
404                 this.getLogger().trace("EXIT!"); //NOI18N
405         }
406
407         /**
408          * Asks the the user to enter a single character which must match validChars
409          *
410          * @param       validChars Valid chars that are accepted
411          * @param       message Message to user
412          * @return      Allowed character
413          */
414         @Override
415         public char enterChar (final char[] validChars, final String message) {
416                 // Trace message
417                 this.getLogger().trace(MessageFormat.format("validChars={0},message={1} - CALLED!", Arrays.toString(validChars), message)); //NOI18N
418
419                 // The validChars must not null be null and filled with at least one char
420                 if (validChars == null) {
421                         // Is null
422                         throw new NullPointerException("validChars is null"); //NOI18N
423                 } else if (validChars.length == 0) {
424                         // Is not filled
425                         throw new IllegalArgumentException("validChars is not filled."); //NOI18N
426                 }
427
428                 char input = 0;
429
430                 // Sort array, else binarySearch() won't work
431                 Arrays.sort(validChars);
432
433                 // Keep asking until valid char has been entered
434                 while (Arrays.binarySearch(validChars, input) < 0) {
435                         // Output message
436                         System.out.print(message);
437
438                         // Read char
439                         input = this.readChar();
440                 }
441
442                 // Trace message
443                 this.getLogger().trace(MessageFormat.format("input={0} - EXIT!", input)); //NOI18N
444
445                 // Return read char
446                 return input;
447         }
448
449         /**
450          * Asks the user to enter his/her gender
451          *
452          * @param message Message to the user
453          * @return Gender enum
454          */
455         @Override
456         public Gender enterGender (final String message) {
457                 // Trace message
458                 this.getLogger().trace(MessageFormat.format("message={0} - CALLED!", message)); //NOI18N
459
460                 // Get valid chars
461                 char[] validChars = Gender.validChars();
462
463                 // Debug message
464                 //* NOISY-DEBUG: */ System.out.println(validChars);
465                 // Call inner method
466                 char gender = this.enterChar(validChars, message);
467
468                 // Now get a Gender instance back
469                 Gender g = Gender.fromChar(gender);
470
471                 // g must not be null
472                 assert(g instanceof Gender) : "g is not set."; //NOI18N
473
474                 // Trace message
475                 this.getLogger().trace(MessageFormat.format("g={0} - EXIT!", g)); //NOI18N
476
477                 // Return it
478                 return g;
479         }
480
481         /**
482          * Reads an integer (int) with a textural message from the user
483          *
484          * @param minimum Minimum allowed number
485          * @param maximum Maximum allowed number
486          * @param message Messager to display in console
487          * @return
488          */
489         @Override
490         public int enterInt (final int minimum, final int maximum, final String message) {
491                 // Trace message
492                 this.getLogger().trace(MessageFormat.format("minimum={0},maximum={1},message={2} - CALLED!", minimum, maximum, message)); //NOI18N
493
494                 // Minimum should not be below zero
495                 assert (minimum >= 0);
496                 assert (maximum > minimum);
497
498                 // Init input
499                 int input = -1;
500
501                 while ((input < minimum) || (input > maximum)) {
502                         // Output message
503                         System.out.print(message);
504
505                         // Read integer from user
506                         input = this.readInt();
507                 }
508
509                 // Trace message
510                 this.getLogger().trace(MessageFormat.format("input={0} - EXIT!", input)); //NOI18N
511
512                 // Return it
513                 return input;
514         }
515
516         /**
517          * Reads a string of minimum and maximum length from the user
518          *
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
524          */
525         @Override
526         public String enterString (final int minLength, final int maxLength, final String message, final boolean allowEmpty) {
527                 // Trace message
528                 this.getLogger().trace(MessageFormat.format("minLength={0},maxLength={1},message={2}allowEmpty={3} - CALLED!", minLength, maxLength, message, allowEmpty)); //NOI18N
529
530                 // Check on length, e.g. country codes are excactly 2 chars long
531                 assert (maxLength >= minLength);
532
533                 // Init input
534                 String input = null;
535
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))) {
538                         // Output message
539                         System.out.print(message);
540
541                         // Read line
542                         input = this.readString();
543                 }
544
545                 // Trace message
546                 this.getLogger().trace(MessageFormat.format("input={0} - EXIT!", input)); //NOI18N
547
548                 // Return it
549                 return input;
550         }
551
552         /**
553          * Returns a console menu item
554          *
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
559          */
560         @Override
561         public SelectableMenuItem getMenuItem (final char accessKey, final String text) {
562                 // Return a new console menu item
563                 return new ConsoleMenuItem(accessKey, text);
564         }
565
566         /**
567          * Initializes this client
568          */
569         @Override
570         public void init () {
571                 // Trace message
572                 this.getLogger().trace("CALLED!"); //NOI18N
573
574                 // Init contact manager here
575                 try {
576                         this.initContactManager();
577                 } catch (final UnsupportedDatabaseBackendException | SQLException ex) {
578                         // End here
579                         this.abortProgramWithException(ex);
580                 }
581
582                 // Fill menu map
583                 this.fillMenuMap();
584
585                 // Trace message
586                 this.getLogger().trace("EXIT!"); //NOI18N
587         }
588
589         /**
590          * Displays textural message to the user
591          *
592          * @param message
593          */
594         @Override
595         public void outputMessage (final String message) {
596                 System.out.println(message);
597         }
598
599         /**
600          * Shows textural menu on console
601          */
602         @Override
603         public void showCurrentMenu () {
604                 this.showMenu(this.getCurrentMenu());
605         }
606
607         /**
608          * Shows given menu entry to user
609          *
610          * @param item Menu entry
611          */
612         @Override
613         public void showEntry (final SelectableMenuItem item) {
614                 // Access key then text
615                 this.outputMessage(MessageFormat.format("[{0}] {1}", item.getAccessKey(), item.getText()));
616         }
617
618         /**
619          * Shows a textural message to the user
620          */
621         @Override
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");
626
627                 // Debug message
628                 this.getLogger().debug("Intro shown to user"); //NOI18N
629         }
630
631         @Override
632         public void userChooseChangeContactData (final Contact contact) throws UnhandledUserChoiceException {
633                 // Trace message
634                 this.getLogger().trace(MessageFormat.format("contact={0} CALLED!", contact)); //NOI18N
635
636                 // Contact must not be null
637                 if (contact == null) {
638                         // Abort here
639                         throw new NullPointerException("contact is null"); //NOI18N
640                 }
641
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) ");
644
645                 // Get manager and cast it
646                 ManageableAddressbookContact manager = (ManageableAddressbookContact) this.getManager();
647
648                 // @TODO Get rid of this ugly switch block, too
649                 switch (choice) {
650                         case 'n': // Name data
651                                 manager.doChangeNameData(contact);
652                                 break;
653
654                         case 'a': // Address data
655                                 manager.doChangeAddressData(contact);
656                                 break;
657
658                         case 'o': // Other data
659                                 manager.doChangeOtherData(contact);
660                                 break;
661
662                         case 'x': // Exit this menu
663                                 // Ignored as it should go back
664                                 break;
665
666                         default:
667                                 // @TODO throw own exception
668                                 throw new UnhandledUserChoiceException(MessageFormat.format("Choice '{0}' not handled yet.", choice)); //NOI18N
669                 }
670
671                 // Trace message
672                 this.getLogger().trace("EXIT!"); //NOI18N
673         }
674
675         /**
676          * Reads one character
677          *
678          * @return A single character
679          */
680         private char readChar () {
681                 // Read line
682                 String input = this.readString();
683
684                 // Debug message
685                 this.getLogger().debug(MessageFormat.format("input={0}", input)); //NOI18N
686
687                 // This must be only one character
688                 if (input.length() != 1) {
689                         // Return zero
690                         return 0;
691                 }
692
693                 // Get char from first (and only) position
694                 return input.charAt(0);
695         }
696
697         /**
698          * Reads an integer (int) from user
699          *
700          * @return An integer number
701          */
702         private int readInt () {
703                 // First read a string
704                 String input = this.readString();
705
706                 // Debug message
707                 this.getLogger().debug(MessageFormat.format("input={0}", input)); //NOI18N
708
709                 // Init number with invalid value
710                 int num = -1;
711
712                 // Parse number, this can be risky
713                 try {
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
718                 }
719
720                 // Trace message
721                 this.getLogger().trace(MessageFormat.format("num={0} - EXIT!", num)); //NOI18N
722
723                 // Return read number
724                 return num;
725         }
726
727         /**
728          * Reads a string from a scanner until RETURN is pressed
729          *
730          * @return Read string from scanner
731          */
732         private String readString () {
733                 return this.scanner.nextLine();
734         }
735
736         /**
737          * Fills menu map with menu entries
738          */
739         @Override
740         protected final void fillMenuMap () {
741                 // Trace message
742                 this.getLogger().trace("CALLED!"); //NOI18N
743
744                 // Initialize first (main) menu
745                 Menu menu = new ConsoleMenu("main", this); //NOI18N
746
747                 // Add it
748                 this.getMenus().put("main", menu); //NOI18N
749
750                 // Trace message
751                 this.getLogger().trace("EXIT!"); //NOI18N
752         }
753 }