X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=Addressbook%2Fsrc%2Forg%2Fmxchange%2Faddressbook%2Fdatabase%2Fbackend%2Fcsv%2FCsvDatabaseBackend.java;h=c5f41e0204a90055b1c426a076c2a3188ee4f858;hb=37fbe3d2cef69140391eb47b50b55dcaf6d5c564;hp=c2caeb3e26f162faf0484bc46304ba05b141c94d;hpb=b868f84e773bc3ab97540e8f1979d8ed559955bb;p=addressbook-swing.git diff --git a/Addressbook/src/org/mxchange/addressbook/database/backend/csv/CsvDatabaseBackend.java b/Addressbook/src/org/mxchange/addressbook/database/backend/csv/CsvDatabaseBackend.java index c2caeb3..c5f41e0 100644 --- a/Addressbook/src/org/mxchange/addressbook/database/backend/csv/CsvDatabaseBackend.java +++ b/Addressbook/src/org/mxchange/addressbook/database/backend/csv/CsvDatabaseBackend.java @@ -1,93 +1,500 @@ -/* - * Copyright (C) 2015 Roland Haeder - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.mxchange.addressbook.database.backend.csv; - -import java.io.DataOutput; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.text.MessageFormat; -import org.mxchange.addressbook.database.storage.Storeable; -import org.mxchange.addressbook.database.backend.DatabaseBackend; -import org.mxchange.addressbook.database.storage.csv.StoreableCsv; - -/** - * A database backend with CSV file as storage implementation - * - * @author Roland Haeder - */ -public class CsvDatabaseBackend extends BaseDatabaseBackend implements DatabaseBackend { - /** - * Output stream for this storage engine - */ - private RandomAccessFile storageFile; - - /** - * Constructor with table name - * - * @param tableName Name of "table" - */ - public CsvDatabaseBackend (final String tableName) { - // Debug message - this.getLogger().debug(MessageFormat.format("Trying to initialize table {0} ...", tableName)); - - // Set table name here, too - this.setTableName(tableName); - - // Construct file name - String fileName = String.format("data/table_%s.csv", tableName); - - // Debug message - this.getLogger().debug(MessageFormat.format("Trying to open file {0} ...", fileName)); - - try { - // Try to initialize the storage (file instance) - this.storageFile = new RandomAccessFile(fileName, "rw"); - } catch (final FileNotFoundException ex) { - // Did not work - this.getLogger().error(MessageFormat.format("File {0} cannot be opened: {1}", fileName, ex.toString())); - System.exit(1); - } - - // Output message - this.getLogger().debug(MessageFormat.format("Database for {0} has been initialized.", tableName)); - } - - /** - * Stores given object by "visiting" it - * - * @param object An object implementing Storeable - * @throws java.io.IOException From "inner" class - */ - @Override - public void store (final Storeable object) throws IOException{ - // Make sure the instance is there (DataOutput flawor) - assert(this.storageFile instanceof DataOutput); - - // Try to cast it, this will fail if the interface is not implemented - StoreableCsv csv = (StoreableCsv) object; - - // Now get the object that needs to be stored - String str = csv.getCsvStringFromStoreableObject(); - - // Debug message - this.getLogger().debug(MessageFormat.format("str({0})={1}", str.length(), str)); - - // The string is now a valid CSV string - this.storageFile.writeUTF(str); - } -} +/* + * Copyright (C) 2015 Roland Haeder + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.mxchange.addressbook.database.backend.csv; + +import java.io.DataOutput; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.StringTokenizer; +import org.mxchange.addressbook.contact.Contact; +import org.mxchange.addressbook.contact.Gender; +import org.mxchange.addressbook.contact.book.BookContact; +import org.mxchange.addressbook.contact.user.UserContact; +import org.mxchange.addressbook.database.backend.BaseDatabaseBackend; +import org.mxchange.addressbook.database.storage.Storeable; +import org.mxchange.addressbook.database.storage.csv.StoreableCsv; +import org.mxchange.addressbook.exceptions.BadTokenException; + +/** + * A database backend with CSV file as storage implementation + * + * @author Roland Haeder + */ +public class CsvDatabaseBackend extends BaseDatabaseBackend implements CsvBackend { + + /** + * Output stream for this storage engine + */ + private RandomAccessFile storageFile; + + /** + * Constructor with table name + * + * @param tableName Name of "table" + */ + public CsvDatabaseBackend (final String tableName) { + // Debug message + this.getLogger().debug(MessageFormat.format("Trying to initialize table {0} ...", tableName)); + + // Set table name here, too + this.setTableName(tableName); + + // Construct file name + String fileName = String.format("data/table_%s.csv", tableName); + + // Debug message + this.getLogger().debug(MessageFormat.format("Trying to open file {0} ...", fileName)); + + try { + // Try to initialize the storage (file instance) + this.storageFile = new RandomAccessFile(fileName, "rw"); + } catch (final FileNotFoundException ex) { + // Did not work + this.getLogger().error(MessageFormat.format("File {0} cannot be opened: {1}", fileName, ex.toString())); + System.exit(1); + } + + // Output message + this.getLogger().debug(MessageFormat.format("Database for {0} has been initialized.", tableName)); + } + + /** + * Gets an iterator for contacts + * + * @return Iterator for contacts + * @throws org.mxchange.addressbook.exceptions.BadTokenException If the + * underlaying method has found an invalid token + */ + @Override + public Iterator contactIterator () throws BadTokenException { + /* + * Then read the file into RAM (yes, not perfect for >1000 entries ...) + * and get a List back. + */ + List list = this.readContactList(); + + // Get iterator from list and return it + return list.iterator(); + } + + /** + * Shuts down this backend + */ + @Override + public void doShutdown () { + try { + // Close file + this.getStorageFile().close(); + } catch (final IOException ex) { + this.getLogger().catching(ex); + System.exit(1); + } + } + + /** + * Get length of underlaying file + * + * @return Length of underlaying file + */ + @Override + public long length () { + long length = 0; + + try { + length = this.getStorageFile().length(); + this.getLogger().debug(MessageFormat.format("length={0}", length)); + } catch (final IOException ex) { + // Length cannot be determined + this.getLogger().catching(ex); + System.exit(1); + } + + // Return result + this.getLogger().trace(MessageFormat.format("length={0} : EXIT!", length)); + return length; + } + + /** + * Rewinds backend + */ + @Override + public void rewind () { + this.getLogger().trace("CALLED!"); + + try { + // Rewind underlaying database file + this.getStorageFile().seek(0); + } catch (final IOException ex) { + this.getLogger().catching(ex); + System.exit(1); + } + + this.getLogger().trace("EXIT!"); + } + + /** + * Stores given object by "visiting" it + * + * @param object An object implementing Storeable + * @throws java.io.IOException From "inner" class + */ + @Override + public void store (final Storeable object) throws IOException { + // Make sure the instance is there (DataOutput flawor) + assert (this.storageFile instanceof DataOutput); + + // Try to cast it, this will fail if the interface is not implemented + StoreableCsv csv = (StoreableCsv) object; + + // Now get a string from the object that needs to be stored + String str = csv.getCsvStringFromStoreableObject(); + + // Debug message + this.getLogger().debug(MessageFormat.format("str({0})={1}", str.length(), str)); + + // The string is now a valid CSV string + this.getStorageFile().writeBytes(str); + } + + /** + * Adds given contact to list + * + * @param contact Contact instance to add + * @param list List instance + */ + private void addContactToList (final Contact contact, final List list) { + // Debug message + this.getLogger().debug(MessageFormat.format("contact={0}", contact)); + + // Is the contact read? + if (contact instanceof Contact) { + // Then add it + boolean added = list.add(contact); + + // Debug message + this.getLogger().debug(MessageFormat.format("contact={0} added={1}", contact, added)); + + // Has it been added? + if (!added) { + // Not added + this.getLogger().warn("Contact object has not been added."); + } + } + } + + /** + * Returns storage file + * + * @return Storage file instance + */ + private RandomAccessFile getStorageFile () { + return this.storageFile; + } + + /** + * Checks whether end of file has been reached + * + * @return Whether lines are left to read + */ + private boolean isEndOfFile () { + // Default is EOF + boolean isEof = true; + + try { + isEof = (this.getStorageFile().getFilePointer() >= this.length()); + } catch (final IOException ex) { + // Length cannot be determined + this.getLogger().catching(ex); + } + + // Return status + this.getLogger().trace(MessageFormat.format("isEof={0} : EXIT!", isEof)); + return isEof; + } + + /** + * Reads the database file, if available, and adds all read lines into the + * list. + * + * @return A list with Contact instances + */ + private List readContactList () throws BadTokenException { + this.getLogger().trace("CALLED!"); + + // First rewind + this.rewind(); + + // Get file size and divide it by 140 (possible average length of one line) + int lines = Math.round(this.length() / 140 + 0.5f); + + // Debug message + this.getLogger().debug(MessageFormat.format("lines={0}", lines)); + + // Instance list + // @TODO The maximum length could be guessed from file size? + List list = new ArrayList<>(lines); + + // Init variables + StringTokenizer tokenizer; + String line; + + // Read all lines + while (!this.isEndOfFile()) { + // Then read a line + line = this.readLine(); + + // Debug message + this.getLogger().debug(MessageFormat.format("line={0}", line)); + + // Then tokenize it + // @TODO Move this into separate method + tokenizer = new StringTokenizer(line, ";"); + + // Count round + int count = 0; + + // Init contact object + Contact contact = null; + + // The tokens are now available, so get all + while (tokenizer.hasMoreElements()) { + // Get next token + String token = tokenizer.nextToken(); + + // Debug message + this.getLogger().debug(MessageFormat.format("token={0}", token)); + + // Verify token, it must have double-quotes on each side + if ((!token.startsWith("\"")) || (!token.endsWith("\""))) { + // Something bad was read + throw new BadTokenException(MessageFormat.format("Token {0} has not double-quotes on both ends.", token)); + } + + // All fine, so remove it + String strippedToken = token.substring(1, token.length() - 1); + + // Is the string's content "null"? + if (strippedToken.equals("null")) { + // Debug message + this.getLogger().debug(MessageFormat.format("strippedToken={0} - NULL!", strippedToken)); + + // This needs to be set to null + strippedToken = null; + } + + // Debug message + this.getLogger().debug(MessageFormat.format("strippedToken={0}", strippedToken)); + + // Init number/string data values + String strData = strippedToken; + Long num = null; + Boolean bool = null; + Gender gender = null; + + // Now, let's try a number check, if no null + if (strippedToken != null) { + // Okay, no null, maybe the string bears a decimal number? + try { + num = Long.valueOf(strippedToken); + + // Debug message + this.getLogger().debug(MessageFormat.format("strippedToken={0} - NUMBER!", strippedToken)); + } catch (final NumberFormatException ex) { + // No number, then set default + num = null; + } + } + + // Now, let's try a boolean check, if no null + if ((strippedToken != null) && (num == null) && ((strippedToken.equals("true")) || (strippedToken.equals("false")))) { + // Debug message + this.getLogger().debug(MessageFormat.format("strippedToken={0} - BOOLEAN!", strippedToken)); + + // parseBoolean() is relaxed, so no exceptions + bool = Boolean.valueOf(strippedToken); + } + + // Now, let's try a boolean check, if no null + if ((strippedToken != null) && (num == null) && (bool == null) && ((strippedToken.equals("M")) || (strippedToken.equals("F")) || (strippedToken.equals("C")))) { + // Get first character + gender = Gender.fromChar(strippedToken.charAt(0)); + } + + // Now it depends on the counter which position we need to check + switch (count) { + case 0: // isOwnContact + assert ((bool instanceof Boolean)); + + // Debug message + this.getLogger().debug(MessageFormat.format("bool={0}", bool)); + + // Is it own contact? + if (true == bool) { + // Debug message + this.getLogger().debug("Creating UserContact object ..."); + + // Own entry + contact = new UserContact(); + } else { + // Debug message + this.getLogger().debug("Creating BookContact object ..."); + + // Other contact + contact = new BookContact(); + } + break; + + case 1: // Gender + assert (contact instanceof Contact) : "First token was not boolean"; + + // Update data + contact.updateNameData(gender, null, null, null); + break; + + case 2: // Surname + assert (contact instanceof Contact) : "First token was not boolean"; + + // Update data + contact.updateNameData(gender, strippedToken, null, null); + break; + + case 3: // Family name + assert (contact instanceof Contact) : "First token was not boolean"; + + // Update data + contact.updateNameData(gender, null, strippedToken, null); + break; + + case 4: // Company name + assert (contact instanceof Contact) : "First token was not boolean"; + + // Update data + contact.updateNameData(gender, null, null, strippedToken); + break; + + case 5: // Street number + assert (contact instanceof Contact) : "First token was not boolean"; + + // Update data + contact.updateAddressData(strippedToken, 0, null, null); + break; + + case 6: // ZIP code + assert (contact instanceof Contact) : "First token was not boolean"; + + // Update data + contact.updateAddressData(null, num, null, null); + break; + + case 7: // City name + assert (contact instanceof Contact) : "First token was not boolean"; + + // Update data + contact.updateAddressData(null, 0, strippedToken, null); + break; + + case 8: // Country code + assert (contact instanceof Contact) : "First token was not boolean"; + + // Update data + contact.updateAddressData(null, 0, null, strippedToken); + break; + + case 9: // Phone number + assert (contact instanceof Contact) : "First token was not boolean"; + + // Update data + contact.updateOtherData(strippedToken, null, null, null, null, null); + break; + + case 10: // Fax number + assert (contact instanceof Contact) : "First token was not boolean"; + + // Update data + contact.updateOtherData(null, strippedToken, null, null, null, null); + break; + + case 11: // Cellphone number + assert (contact instanceof Contact) : "First token was not boolean"; + + // Update data + contact.updateOtherData(null, null, strippedToken, null, null, null); + break; + + case 12: // Email address + assert (contact instanceof Contact) : "First token was not boolean"; + + // Update data + contact.updateOtherData(null, null, null, strippedToken, null, null); + break; + + case 13: // Birthday + assert (contact instanceof Contact) : "First token was not boolean"; + + // Update data + contact.updateOtherData(null, null, null, null, strippedToken, null); + break; + + case 14: // Birthday + assert (contact instanceof Contact) : "First token was not boolean"; + + // Update data + contact.updateOtherData(null, null, null, null, null, strippedToken); + break; + + default: // New data entry + this.getLogger().warn(MessageFormat.format("Will not handle unknown data {0} at index {1}", strippedToken, count)); + break; + } + + // Increment counter for next round + count++; + } + + // Add contact + this.addContactToList(contact, list); + } + + // Return finished list + this.getLogger().trace(MessageFormat.format("list.size()={0} : EXIT!", list.size())); + return list; + } + + /** + * Reads a line from file base + * + * @return Read line from file + */ + private String readLine () { + // Init input + String input = null; + + try { + input = this.getStorageFile().readLine(); + } catch (final IOException ex) { + this.getLogger().catching(ex); + } + + // Return read string or null + return input; + } +}