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.database.frontend.contact;
19 import java.io.IOException;
20 import java.sql.SQLException;
21 import java.text.MessageFormat;
22 import java.util.Iterator;
23 import java.util.StringTokenizer;
24 import org.mxchange.addressbook.contact.book.BookContact;
25 import org.mxchange.addressbook.contact.user.UserContact;
26 import org.mxchange.addressbook.database.contact.AddressbookContactDatabaseConstants;
27 import org.mxchange.addressbook.exceptions.ContactAlreadyAddedException;
28 import org.mxchange.addressbook.manager.contact.AddressbookContactManager;
29 import org.mxchange.jcore.contact.Contact;
30 import org.mxchange.jcore.contact.Gender;
31 import org.mxchange.jcore.criteria.searchable.SearchCriteria;
32 import org.mxchange.jcore.criteria.searchable.SearchableCritera;
33 import org.mxchange.jcore.database.frontend.BaseDatabaseFrontend;
34 import org.mxchange.jcore.database.result.Result;
35 import org.mxchange.jcore.database.storage.Storeable;
36 import org.mxchange.jcore.exceptions.BadTokenException;
37 import org.mxchange.jcore.exceptions.UnsupportedDatabaseBackendException;
40 * Stores and retrieves Contact instances
42 * @author Roland Haeder
44 public class AddressbookContactDatabaseFrontend extends BaseDatabaseFrontend implements AddressbookContactFrontend {
47 * Constructor which accepts a contact manager
49 * @param manager Manager instance
50 * @throws org.mxchange.jcore.exceptions.UnsupportedDatabaseBackendException If the database backend is not supported
51 * @throws java.sql.SQLException If any SQL error occurs
53 public AddressbookContactDatabaseFrontend (final AddressbookContactManager manager) throws UnsupportedDatabaseBackendException, SQLException {
54 // Call own constructor
58 this.getLogger().trace(MessageFormat.format("manager={0} - CALLED!", manager)); //NOI18N
60 // Manager instance must not be null
61 if (manager == null) {
63 throw new NullPointerException("manager is null"); //NOI18N
66 // Set contact manager
67 this.setContactManager(manager);
71 * Default but protected constructor
72 * @throws org.mxchange.jcore.exceptions.UnsupportedDatabaseBackendException If the database backend is not supported
73 * @throws java.sql.SQLException Any SQL exception from e.g. MySQL connector
75 protected AddressbookContactDatabaseFrontend () throws UnsupportedDatabaseBackendException, SQLException {
77 this.getLogger().trace("CALLED!"); //NOI18N
80 this.setTableName("contacts"); //NOI18N
87 * Adds given contact instance to database
89 * @param contact Contact instance
90 * @throws org.mxchange.addressbook.exceptions.ContactAlreadyAddedException If the contact instance is already found
93 public void addContact (final Contact contact) throws ContactAlreadyAddedException {
95 this.getLogger().trace("CALLED!"); //NOI18N
97 // Make sure the contact is set
98 if (contact == null) {
100 throw new NullPointerException("contact is null"); //NOI18N
104 // First check if the contact is there
105 if (this.isContactFound(contact)) {
107 throw new ContactAlreadyAddedException(contact);
111 this.getBackend().store((Storeable) contact);
112 } catch (final IOException | BadTokenException ex) {
114 this.abortProgramWithException(ex);
118 this.getLogger().trace("CALLED!"); //NOI18N
122 * Shuts down the database layer
125 public void doShutdown () throws SQLException, IOException {
127 this.getLogger().trace("CALLED!"); //NOI18N
130 this.getBackend().doShutdown();
133 this.getLogger().trace("EXIT!"); //NOI18N
137 * Some "getter" for total contact count
139 * @return Total contact count
142 public int getContactsCount () throws SQLException {
143 // And deligate to backend
144 return this.getBackend().getTotalCount(); //NOI18N
148 * Some "getter" for own contact instance
150 * @return Own contact instance
153 public Contact getOwnContact () {
155 this.getLogger().trace("CALLED!"); //NOI18N
157 // Get row index back from backend
158 int rowIndex = this.getBackend().getRowIndexFromColumn(AddressbookContactDatabaseConstants.COLUMN_NAME_OWN_CONTACT, true);
161 this.getLogger().debug(MessageFormat.format("rowIndex={0}", rowIndex));
164 Contact contact = null;
167 // Now simply read the row
168 contact = (Contact) this.getBackend().readRow(rowIndex);
169 } catch (final BadTokenException ex) {
171 this.abortProgramWithException(ex);
175 this.getLogger().trace(MessageFormat.format("contact={0} - EXIT!", contact));
182 * Checks if given Contact is found
184 * @param contact Contact instance to check
185 * @return Whether the given Contact instance is found
188 public boolean isContactFound (final Contact contact) throws BadTokenException {
190 this.getLogger().trace(MessageFormat.format("contact={0} - CALLED!", contact)); //NOI18N
192 // contact should not be null
193 if (contact == null) {
195 throw new NullPointerException("contact is null"); //NOI18N
198 // Default is not found
199 boolean isFound = false;
202 Iterator<? extends Storeable> iterator = this.getBackend().iterator();
205 while (iterator.hasNext()) {
207 Contact c = (Contact) iterator.next();
210 this.getLogger().debug(MessageFormat.format("c={0},contact={1}", c, contact)); //NOI18N
213 if (c.equals(contact)) {
221 this.getLogger().trace(MessageFormat.format("isFound={0} - EXIT!", isFound)); //NOI18N
228 * Checks whether own contact is found in database
230 * @return Whether own contact is found
231 * @throws java.io.IOException If any IO error occurs
232 * @throws org.mxchange.jcore.exceptions.BadTokenException If a bad token was found
235 public boolean isOwnContactFound () throws SQLException, IOException, BadTokenException {
236 // Get search criteria instance
237 SearchableCritera critera = new SearchCriteria();
240 critera.addCriteria(AddressbookContactDatabaseConstants.COLUMN_NAME_OWN_CONTACT, true);
243 Result<? extends Storeable> result = this.getBackend().doSelectByCriteria(critera);
245 // Deligate this call to backend
246 return result.hasNext();
250 * Parses given line from database backend into a Storeable instance. Please
251 * note that not all backends need this.
253 * @param line Line from database backend
254 * @return A Storeable instance
257 public Storeable parseLineToStoreable (final String line) throws BadTokenException {
259 this.getLogger().trace(MessageFormat.format("line={0} - CALLED!", line)); //NOI18N
262 Contact contact = this.parseLineToContact(line);
265 this.getLogger().debug(MessageFormat.format("contact={0}", contact));
268 return (Storeable) contact;
272 * Reads a single row and parses it to a contact instance
274 * @param rowIndex Row index (also how much to skip)
275 * @return Contact instance
278 public Contact readSingleContact (final int rowIndex) {
280 // Deligate this to backend instance
281 return (Contact) this.getBackend().readRow(rowIndex);
282 } catch (final BadTokenException ex) {
284 this.abortProgramWithException(ex);
287 // Bad state, should not be reached
288 throw new IllegalStateException("This should not be reached");
292 * Parses given line and creates a Contact instance
294 * @param line Raw line to parse
296 * @return Contact instance
298 private Contact parseLineToContact (final String line) throws BadTokenException {
300 this.getLogger().trace(MessageFormat.format("line={0} - CALLED!", line)); //NOI18N
302 // Init A lot variables
305 Gender gender = null;
307 Contact contact = null;
310 this.getLogger().debug(MessageFormat.format("line={0}", line)); //NOI18N
313 // @TODO Move this into separate method
314 StringTokenizer tokenizer = new StringTokenizer(line, ";"); //NOI18N
320 // The tokens are now available, so get all
321 while (tokenizer.hasMoreElements()) {
327 String token = tokenizer.nextToken();
329 // If char " is at pos 2 (0,1,2), then cut it of there
330 if ((token.charAt(0) != '"') && (token.charAt(2) == '"')) {
331 // UTF-8 writer characters found
332 token = token.substring(2);
336 this.getLogger().debug(MessageFormat.format("token={0}", token)); //NOI18N
338 // Verify token, it must have double-quotes on each side
339 if ((!token.startsWith("\"")) || (!token.endsWith("\""))) { //NOI18N
340 // Something bad was read
341 throw new BadTokenException(token, count); //NOI18N
344 // All fine, so remove it
345 String strippedToken = token.substring(1, token.length() - 1);
347 // Is the string's content "null"?
348 if (strippedToken.equals("null")) { //NOI18N
350 this.getLogger().debug(MessageFormat.format("strippedToken={0} - NULL!", strippedToken)); //NOI18N
352 // This needs to be set to null
353 strippedToken = null;
357 this.getLogger().debug(MessageFormat.format("strippedToken={0}", strippedToken)); //NOI18N
359 // Now, let's try a number check, if no null
360 if (strippedToken != null) {
361 // Okay, no null, maybe the string bears a decimal number?
363 num = Long.valueOf(strippedToken);
366 this.getLogger().debug(MessageFormat.format("strippedToken={0} - NUMBER!", strippedToken)); //NOI18N
367 } catch (final NumberFormatException ex) {
368 // No number, then set default
373 // Now, let's try a boolean check, if no null
374 if ((strippedToken != null) && (num == null) && ((strippedToken.equals("true")) || (strippedToken.equals("false")))) { //NOI18N
376 this.getLogger().debug(MessageFormat.format("strippedToken={0} - BOOLEAN!", strippedToken)); //NOI18N
378 // parseBoolean() is relaxed, so no exceptions
379 bool = Boolean.valueOf(strippedToken);
383 this.getLogger().debug(MessageFormat.format("strippedToken={0},num={1},bool={2}", strippedToken, num, bool)); //NOI18N
385 // Now, let's try a gender check, if no null
386 if ((strippedToken != null) && (num == null) && (bool == null) && (Gender.valueOf(strippedToken) instanceof Gender)) { //NOI18N
387 // Get first character
388 gender = Gender.valueOf(strippedToken);
391 this.getLogger().debug(MessageFormat.format("strippedToken={0},gender={1}", strippedToken, gender)); //NOI18N
393 // This instance must be there
394 assert (gender instanceof Gender) : MessageFormat.format("gender is not set by Gender.fromChar({0})", strippedToken); //NOI18N
397 // Now it depends on the counter which position we need to check
399 case 0: // isOwnContact
400 assert ((bool instanceof Boolean));
403 this.getLogger().debug(MessageFormat.format("bool={0}", bool)); //NOI18N
405 // Is it own contact?
408 this.getLogger().debug("Creating UserContact object ..."); //NOI18N
411 contact = new UserContact();
414 this.getLogger().debug("Creating BookContact object ..."); //NOI18N
417 contact = new BookContact();
422 assert (contact instanceof Contact) : "First token was not boolean"; //NOI18N
425 contact.setGender(gender);
429 assert (contact instanceof Contact) : "First token was not boolean"; //NOI18N
430 assert (gender instanceof Gender) : "gender instance is not set"; //NOI18N
433 contact.setSurname(strippedToken);
436 case 3: // Family name
437 assert (contact instanceof Contact) : "First token was not boolean"; //NOI18N
438 assert (gender instanceof Gender) : "gender instance is not set"; //NOI18N
441 contact.setFamilyName(strippedToken);
444 case 4: // Company name
445 assert (contact instanceof Contact) : "First token was not boolean"; //NOI18N
446 assert (gender instanceof Gender) : "gender instance is not set"; //NOI18N
449 contact.setCompanyName(strippedToken);
452 case 5: // Street number
453 assert (contact instanceof Contact) : "First token was not boolean"; //NOI18N
456 contact.setHouseNumber(num);
460 assert (contact instanceof Contact) : "First token was not boolean"; //NOI18N
463 contact.setZipCode(num);
467 assert (contact instanceof Contact) : "First token was not boolean"; //NOI18N
470 contact.setCity(strippedToken);
473 case 8: // Country code
474 assert (contact instanceof Contact) : "First token was not boolean"; //NOI18N
477 contact.setCountryCode(strippedToken);
480 case 9: // Phone number
481 assert (contact instanceof Contact) : "First token was not boolean"; //NOI18N
484 contact.setPhoneNumber(strippedToken);
487 case 10: // Fax number
488 assert (contact instanceof Contact) : "First token was not boolean"; //NOI18N
491 contact.setFaxNumber(strippedToken);
494 case 11: // Cellphone number
495 assert (contact instanceof Contact) : "First token was not boolean"; //NOI18N
498 contact.setCellphoneNumber(strippedToken);
501 case 12: // Email address
502 assert (contact instanceof Contact) : "First token was not boolean"; //NOI18N
505 contact.setEmailAddress(strippedToken);
509 assert (contact instanceof Contact) : "First token was not boolean"; //NOI18N
512 contact.setBirthday(strippedToken);
516 assert (contact instanceof Contact) : "First token was not boolean"; //NOI18N
519 contact.setComment(strippedToken);
522 default: // New data entry
523 this.getLogger().warn(MessageFormat.format("Will not handle unknown data {0} at index {1}", strippedToken, count)); //NOI18N
527 // Increment counter for next round
532 this.getLogger().trace(MessageFormat.format("contact={0} - EXIT!", contact)); //NOI18N
534 // Return finished instance