import java.io.IOException;\r
import java.io.RandomAccessFile;\r
import java.text.MessageFormat;\r
+import java.util.ArrayList;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+import java.util.StringTokenizer;\r
+import org.mxchange.addressbook.BadTokenException;\r
+import org.mxchange.addressbook.contact.Contact;\r
+import org.mxchange.addressbook.contact.book.BookContact;\r
+import org.mxchange.addressbook.contact.user.UserContact;\r
+import org.mxchange.addressbook.database.backend.BaseDatabaseBackend;\r
import org.mxchange.addressbook.database.storage.Storeable;\r
-import org.mxchange.addressbook.database.backend.DatabaseBackend;\r
import org.mxchange.addressbook.database.storage.csv.StoreableCsv;\r
\r
/**\r
* \r
* @author Roland Haeder\r
*/\r
-public class CsvDatabaseBackend extends BaseDatabaseBackend implements DatabaseBackend {\r
+public class CsvDatabaseBackend extends BaseDatabaseBackend implements CsvBackend {\r
/**\r
* Output stream for this storage engine\r
*/\r
this.getLogger().debug(MessageFormat.format("Database for {0} has been initialized.", tableName));\r
}\r
\r
+ /**\r
+ * Gets an iterator for contacts\r
+ * \r
+ * @return Iterator for contacts\r
+ * @throws org.mxchange.addressbook.BadTokenException If the underlaying method has found an invalid token\r
+ */\r
+ @Override\r
+ public Iterator<Contact> contactIterator () throws BadTokenException {\r
+ /*\r
+ * Then read the file into RAM (yes, not perfect for >1000 entries ...)\r
+ * and get a List back.\r
+ */\r
+ List<Contact> list = this.readContactList();\r
+\r
+ // Get iterator from list and return it\r
+ return list.iterator();\r
+ }\r
+\r
+ /**\r
+ * Get length of underlaying file\r
+ *\r
+ * @return Length of underlaying file\r
+ */\r
+ @Override\r
+ public long length () {\r
+ long length = 0;\r
+ \r
+ try {\r
+ length = this.storageFile.length();\r
+ this.getLogger().debug(MessageFormat.format("length={0}", length));\r
+ } catch (final IOException ex) {\r
+ // Length cannot be determined\r
+ this.getLogger().catching(ex);\r
+ System.exit(1);\r
+ }\r
+ \r
+ // Return result\r
+ this.getLogger().trace(MessageFormat.format("length={0} : EXIT!", length));\r
+ return length;\r
+ }\r
+\r
+ /**\r
+ * Rewinds backend\r
+ */\r
+ @Override\r
+ public void rewind (){\r
+ this.getLogger().trace("CALLED!");\r
+\r
+ try {\r
+ // Rewind underlaying database file\r
+ this.storageFile.seek(0);\r
+ } catch (final IOException ex) {\r
+ this.getLogger().catching(ex);\r
+ System.exit(1);\r
+ }\r
+\r
+ this.getLogger().trace("EXIT!");\r
+ }\r
+\r
/**\r
* Stores given object by "visiting" it\r
*\r
* @throws java.io.IOException From "inner" class\r
*/\r
@Override\r
- public void store (final Storeable object) throws IOException{\r
+ public void store (final Storeable object) throws IOException {\r
// Make sure the instance is there (DataOutput flawor)\r
assert(this.storageFile instanceof DataOutput);\r
\r
// Try to cast it, this will fail if the interface is not implemented\r
StoreableCsv csv = (StoreableCsv) object;\r
\r
- // Now get the object that needs to be stored\r
+ // Now get a string from the object that needs to be stored\r
String str = csv.getCsvStringFromStoreableObject();\r
\r
// Debug message\r
this.getLogger().debug(MessageFormat.format("str({0})={1}", str.length(), str));\r
\r
// The string is now a valid CSV string\r
- this.storageFile.writeUTF(str);\r
+ this.storageFile.writeChars(str);\r
+ }\r
+\r
+ /**\r
+ * Checks whether end of file has been reached\r
+ * \r
+ * @return Whether lines are left to read\r
+ */\r
+ private boolean isEndOfFile () {\r
+ // Default is EOF\r
+ boolean isEof = true;\r
+\r
+ try {\r
+ isEof = (this.storageFile.getFilePointer() >= this.length());\r
+ } catch (final IOException ex) {\r
+ // Length cannot be determined\r
+ this.getLogger().catching(ex);\r
+ }\r
+\r
+ // Return status\r
+ this.getLogger().trace(MessageFormat.format("isEof={0} : EXIT!", isEof));\r
+ return isEof;\r
+ }\r
+\r
+ /**\r
+ * Reads the database file, if available, and adds all read lines into\r
+ * the list.\r
+ * \r
+ * @return A list with Contact instances\r
+ */\r
+ private List<Contact> readContactList () throws BadTokenException {\r
+ this.getLogger().trace("CALLED!");\r
+\r
+ // First rewind\r
+ this.rewind();\r
+\r
+ // Instance list\r
+ // @TODO The maximum length could be guessed from file size?\r
+ List<Contact> list = new ArrayList<>();\r
+\r
+ // Init variables\r
+ StringTokenizer tokenizer;\r
+ String line;\r
+\r
+ // Read all lines\r
+ while (!this.isEndOfFile()) {\r
+ // Then read a line\r
+ line = this.readLine();\r
+\r
+ // Debug message\r
+ this.getLogger().debug(MessageFormat.format("line={0}", line));\r
+\r
+ // Then tokenize it\r
+ // @TODO Move this into separate method\r
+ tokenizer = new StringTokenizer(line, ";");\r
+\r
+ // Count round\r
+ int count = 0;\r
+\r
+ // Init contact object\r
+ Contact contact = null;\r
+\r
+ // The tokens are now available, so get all\r
+ while (tokenizer.hasMoreElements()) {\r
+ // Get next token\r
+ String token = tokenizer.nextToken();\r
+\r
+ // Debug message\r
+ this.getLogger().debug(MessageFormat.format("token={0}", token));\r
+\r
+ // Verify token, it must have double-quotes on each side\r
+ if ((!token.startsWith("\"")) || (!token.endsWith("\""))) {\r
+ // Something bad was read\r
+ throw new BadTokenException(MessageFormat.format("Token {0} has not double-quotes on both ends.", token));\r
+ }\r
+\r
+ // All fine, so remove it\r
+ String strippedToken = token.substring(1, token.length() - 1);\r
+\r
+ // Is the string's content "null"?\r
+ if (strippedToken.equals("null")) {\r
+ // Debug message\r
+ this.getLogger().debug(MessageFormat.format("strippedToken={0} - NULL!", strippedToken));\r
+\r
+ // This needs to be set to null\r
+ strippedToken = null;\r
+ }\r
+\r
+ // Debug message\r
+ this.getLogger().debug(MessageFormat.format("strippedToken={0}", strippedToken));\r
+\r
+ // Init number/string data values\r
+ String strData = strippedToken;\r
+ Long num = null;\r
+ Boolean bool = null;\r
+ char gender = '?';\r
+ \r
+ // Now, let's try a number check, if no null\r
+ if (strippedToken != null) {\r
+ // Okay, no null, maybe the string bears a decimal number?\r
+ try {\r
+ num = Long.valueOf(strippedToken);\r
+\r
+ // Debug message\r
+ this.getLogger().debug(MessageFormat.format("strippedToken={0} - NUMBER!", strippedToken));\r
+ } catch (final NumberFormatException ex) {\r
+ // No number, then set default\r
+ num = null;\r
+ }\r
+ }\r
+ \r
+ // Now, let's try a boolean check, if no null\r
+ if ((strippedToken != null) && (num == null) && ((strippedToken.equals("true")) || (strippedToken.equals("false")))) {\r
+ // Debug message\r
+ this.getLogger().debug(MessageFormat.format("strippedToken={0} - BOOLEAN!", strippedToken));\r
+\r
+ // parseBoolean() is relaxed, so no exceptions\r
+ bool = Boolean.valueOf(strippedToken);\r
+ }\r
+ \r
+ // Now, let's try a boolean check, if no null\r
+ if ((strippedToken != null) && (num == null) && (bool == null) && ((strippedToken.equals("M")) || (strippedToken.equals("F")) || (strippedToken.equals("C")))) {\r
+ // Get first character\r
+ gender = strippedToken.charAt(0);\r
+ }\r
+\r
+ // Now it depends on the counter which position we need to check\r
+ switch (count) {\r
+ case 0: // isOwnContact\r
+ assert((bool instanceof Boolean));\r
+\r
+ // Debug message\r
+ this.getLogger().debug(MessageFormat.format("bool={0}", bool));\r
+\r
+ // Is it own contact?\r
+ if (true == bool) {\r
+ // Debug message\r
+ this.getLogger().debug("Creating UserContact object ...");\r
+\r
+ // Own entry\r
+ contact = new UserContact();\r
+ } else {\r
+ // Debug message\r
+ this.getLogger().debug("Creating BookContact object ...");\r
+\r
+ // Other contact\r
+ contact = new BookContact();\r
+ }\r
+ break;\r
+\r
+ case 1: // Gender\r
+ assert(contact instanceof Contact) : "First token was not boolean";\r
+ assert(gender != '?') : "Gender is not detected.";\r
+\r
+ // Update data\r
+ contact.updateNameData(gender, null, null, null);\r
+ break;\r
+\r
+ case 2: // Surname\r
+ assert(contact instanceof Contact) : "First token was not boolean";\r
+ assert(gender != '?') : "Gender is not detected.";\r
+\r
+ // Update data\r
+ contact.updateNameData(gender, strippedToken, null, null);\r
+ break;\r
+\r
+ case 3: // Family name\r
+ assert(contact instanceof Contact) : "First token was not boolean";\r
+ assert(gender != '?') : "Gender is not detected.";\r
+\r
+ // Update data\r
+ contact.updateNameData(gender, null, strippedToken, null);\r
+ break;\r
+\r
+ case 4: // Company name\r
+ assert(contact instanceof Contact) : "First token was not boolean";\r
+ assert(gender != '?') : "Gender is not detected.";\r
+\r
+ // Update data\r
+ contact.updateNameData(gender, null, null, strippedToken);\r
+ break;\r
+\r
+ case 5: // Street number\r
+ assert(contact instanceof Contact) : "First token was not boolean";\r
+\r
+ // Update data\r
+ contact.updateAddressData(strippedToken, 0, null, null);\r
+ break;\r
+\r
+ case 6: // ZIP code\r
+ assert(contact instanceof Contact) : "First token was not boolean";\r
+\r
+ // Update data\r
+ contact.updateAddressData(null, num, null, null);\r
+ break;\r
+\r
+ case 7: // City name\r
+ assert(contact instanceof Contact) : "First token was not boolean";\r
+\r
+ // Update data\r
+ contact.updateAddressData(null, 0, strippedToken, null);\r
+ break;\r
+\r
+ case 8: // Country code\r
+ assert(contact instanceof Contact) : "First token was not boolean";\r
+\r
+ // Update data\r
+ contact.updateAddressData(null, 0, null, strippedToken);\r
+ break;\r
+\r
+ case 9: // Phone number\r
+ assert(contact instanceof Contact) : "First token was not boolean";\r
+\r
+ // Update data\r
+ contact.updateOtherData(strippedToken, null, null, null, null, null);\r
+ break;\r
+\r
+ case 10: // Fax number\r
+ assert(contact instanceof Contact) : "First token was not boolean";\r
+\r
+ // Update data\r
+ contact.updateOtherData(null, strippedToken, null, null, null, null);\r
+ break;\r
+\r
+ case 11: // Cellphone number\r
+ assert(contact instanceof Contact) : "First token was not boolean";\r
+\r
+ // Update data\r
+ contact.updateOtherData(null, null, strippedToken, null, null, null);\r
+ break;\r
+\r
+ case 12: // Email address\r
+ assert(contact instanceof Contact) : "First token was not boolean";\r
+\r
+ // Update data\r
+ contact.updateOtherData(null, null, null, strippedToken, null, null);\r
+ break;\r
+\r
+ case 13: // Birthday\r
+ assert(contact instanceof Contact) : "First token was not boolean";\r
+\r
+ // Update data\r
+ contact.updateOtherData(null, null, null, null, strippedToken, null);\r
+ break;\r
+\r
+ case 14: // Birthday\r
+ assert(contact instanceof Contact) : "First token was not boolean";\r
+\r
+ // Update data\r
+ contact.updateOtherData(null, null, null, null, null, strippedToken);\r
+ break;\r
+\r
+ default: // New data entry\r
+ this.getLogger().warn(MessageFormat.format("Will not handle unknown data {0} at index {1}", strippedToken, count));\r
+ break;\r
+ }\r
+\r
+ // Increment counter for next round\r
+ count++;\r
+ }\r
+\r
+ // Is the contact read?\r
+ if (contact instanceof Contact) {\r
+ // Then add it\r
+ assert(list.add(contact));\r
+ }\r
+ }\r
+\r
+ // Return finished list\r
+ this.getLogger().trace(MessageFormat.format("list.size()={0} : EXIT!", list.size()));\r
+ return list;\r
+ }\r
+\r
+ /**\r
+ * Reads a line from file base\r
+ *\r
+ * @return Read line from file\r
+ */\r
+ private String readLine () {\r
+ // Init input\r
+ String input = null;\r
+\r
+ try {\r
+ input = this.storageFile.readLine();\r
+ } catch (final IOException ex) {\r
+ this.getLogger().catching(ex);\r
+ }\r
+\r
+ // Return read string or null\r
+ return input;\r
}\r
}\r