]> git.mxchange.org Git - jfinancials-lib.git/blob - Addressbook/src/org/mxchange/addressbook/database/backend/csv/CsvDatabaseBackend.java
the contact manager instance only exists when initFrame() is called and not on object...
[jfinancials-lib.git] / Addressbook / src / org / mxchange / addressbook / database / backend / csv / CsvDatabaseBackend.java
1 /*\r
2  * Copyright (C) 2015 Roland Haeder\r
3  *\r
4  * This program is free software: you can redistribute it and/or modify\r
5  * it under the terms of the GNU General Public License as published by\r
6  * the Free Software Foundation, either version 3 of the License, or\r
7  * (at your option) any later version.\r
8  *\r
9  * This program is distributed in the hope that it will be useful,\r
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
12  * GNU General Public License for more details.\r
13  *\r
14  * You should have received a copy of the GNU General Public License\r
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.\r
16  */\r
17 package org.mxchange.addressbook.database.backend.csv;\r
18 \r
19 import java.io.DataOutput;\r
20 import java.io.FileNotFoundException;\r
21 import java.io.IOException;\r
22 import java.io.RandomAccessFile;\r
23 import java.text.MessageFormat;\r
24 import java.util.ArrayList;\r
25 import java.util.Iterator;\r
26 import java.util.List;\r
27 import java.util.StringTokenizer;\r
28 import org.mxchange.addressbook.BadTokenException;\r
29 import org.mxchange.addressbook.contact.Contact;\r
30 import org.mxchange.addressbook.contact.book.BookContact;\r
31 import org.mxchange.addressbook.contact.user.UserContact;\r
32 import org.mxchange.addressbook.database.backend.BaseDatabaseBackend;\r
33 import org.mxchange.addressbook.database.storage.Storeable;\r
34 import org.mxchange.addressbook.database.storage.csv.StoreableCsv;\r
35 \r
36 /**\r
37  * A database backend with CSV file as storage implementation\r
38  * \r
39  * @author Roland Haeder\r
40  */\r
41 public class CsvDatabaseBackend extends BaseDatabaseBackend implements CsvBackend {\r
42     /**\r
43      * Output stream for this storage engine\r
44      */\r
45     private RandomAccessFile storageFile;\r
46 \r
47     /**\r
48      * Constructor with table name\r
49      * \r
50      * @param tableName Name of "table"\r
51      */\r
52     public CsvDatabaseBackend (final String tableName) {\r
53         // Debug message\r
54         this.getLogger().debug(MessageFormat.format("Trying to initialize table {0} ...", tableName));\r
55 \r
56         // Set table name here, too\r
57         this.setTableName(tableName);\r
58 \r
59         // Construct file name\r
60         String fileName = String.format("data/table_%s.csv", tableName);\r
61 \r
62         // Debug message\r
63         this.getLogger().debug(MessageFormat.format("Trying to open file {0} ...", fileName));\r
64 \r
65         try {\r
66             // Try to initialize the storage (file instance)\r
67             this.storageFile = new RandomAccessFile(fileName, "rw");\r
68         } catch (final FileNotFoundException ex) {\r
69             // Did not work\r
70             this.getLogger().error(MessageFormat.format("File {0} cannot be opened: {1}", fileName, ex.toString()));\r
71             System.exit(1);\r
72         }\r
73 \r
74         // Output message\r
75         this.getLogger().debug(MessageFormat.format("Database for {0} has been initialized.", tableName));\r
76     }\r
77 \r
78     /**\r
79      * Gets an iterator for contacts\r
80      * \r
81      * @return Iterator for contacts\r
82      * @throws org.mxchange.addressbook.BadTokenException If the underlaying method has found an invalid token\r
83      */\r
84     @Override\r
85     public Iterator<Contact> contactIterator () throws BadTokenException {\r
86         /*\r
87          * Then read the file into RAM (yes, not perfect for >1000 entries ...)\r
88          * and get a List back.\r
89          */\r
90         List<Contact> list = this.readContactList();\r
91 \r
92         // Get iterator from list and return it\r
93         return list.iterator();\r
94     }\r
95 \r
96     /**\r
97      * Shuts down this backend\r
98      */\r
99     @Override\r
100     public void doShutdown () {\r
101         try {\r
102             // Close file\r
103             this.storageFile.close();\r
104         } catch (final IOException ex) {\r
105             this.getLogger().catching(ex);\r
106             System.exit(1);\r
107         }\r
108     }\r
109 \r
110     /**\r
111      * Get length of underlaying file\r
112      *\r
113      * @return Length of underlaying file\r
114      */\r
115     @Override\r
116     public long length () {\r
117         long length = 0;\r
118         \r
119         try {\r
120             length = this.storageFile.length();\r
121             this.getLogger().debug(MessageFormat.format("length={0}", length));\r
122         } catch (final IOException ex) {\r
123             // Length cannot be determined\r
124             this.getLogger().catching(ex);\r
125             System.exit(1);\r
126         }\r
127         \r
128         // Return result\r
129         this.getLogger().trace(MessageFormat.format("length={0} : EXIT!", length));\r
130         return length;\r
131     }\r
132 \r
133     /**\r
134      * Rewinds backend\r
135      */\r
136     @Override\r
137     public void rewind (){\r
138         this.getLogger().trace("CALLED!");\r
139 \r
140         try {\r
141             // Rewind underlaying database file\r
142             this.storageFile.seek(0);\r
143         } catch (final IOException ex) {\r
144             this.getLogger().catching(ex);\r
145             System.exit(1);\r
146         }\r
147 \r
148         this.getLogger().trace("EXIT!");\r
149     }\r
150 \r
151     /**\r
152      * Stores given object by "visiting" it\r
153      *\r
154      * @param object An object implementing Storeable\r
155      * @throws java.io.IOException From "inner" class\r
156      */\r
157     @Override\r
158     public void store (final Storeable object) throws IOException {\r
159         // Make sure the instance is there (DataOutput flawor)\r
160         assert(this.storageFile instanceof DataOutput);\r
161 \r
162         // Try to cast it, this will fail if the interface is not implemented\r
163         StoreableCsv csv = (StoreableCsv) object;\r
164 \r
165         // Now get a string from the object that needs to be stored\r
166         String str = csv.getCsvStringFromStoreableObject();\r
167 \r
168         // Debug message\r
169         this.getLogger().debug(MessageFormat.format("str({0})={1}", str.length(), str));\r
170 \r
171         // The string is now a valid CSV string\r
172         this.storageFile.writeBytes(str);\r
173     }\r
174 \r
175     /**\r
176      * Adds given contact to list\r
177      * \r
178      * @param contact Contact instance to add\r
179      * @param list List instance\r
180      */\r
181     private void addContactToList (final Contact contact, final List<Contact> list) {\r
182         // Debug message\r
183         this.getLogger().debug(MessageFormat.format("contact={0}", contact));\r
184 \r
185         // Is the contact read?\r
186         if (contact instanceof Contact) {\r
187             // Then add it\r
188             boolean added = list.add(contact);\r
189 \r
190             // Debug message\r
191             this.getLogger().debug(MessageFormat.format("contact={0} added={1}", contact, added));\r
192 \r
193             // Has it been added?\r
194             if (!added) {\r
195                 // Not added\r
196                 this.getLogger().warn("Contact object has not been added.");\r
197             }\r
198         }\r
199     }\r
200 \r
201     /**\r
202      * Checks whether end of file has been reached\r
203      * \r
204      * @return Whether lines are left to read\r
205      */\r
206     private boolean isEndOfFile () {\r
207         // Default is EOF\r
208         boolean isEof = true;\r
209 \r
210         try {\r
211             isEof = (this.storageFile.getFilePointer() >= this.length());\r
212         } catch (final IOException ex) {\r
213             // Length cannot be determined\r
214             this.getLogger().catching(ex);\r
215         }\r
216 \r
217         // Return status\r
218         this.getLogger().trace(MessageFormat.format("isEof={0} : EXIT!", isEof));\r
219         return isEof;\r
220     }\r
221 \r
222     /**\r
223      * Reads the database file, if available, and adds all read lines into\r
224      * the list.\r
225      * \r
226      * @return A list with Contact instances\r
227      */\r
228     private List<Contact> readContactList () throws BadTokenException {\r
229         this.getLogger().trace("CALLED!");\r
230 \r
231         // First rewind\r
232         this.rewind();\r
233 \r
234         // Get file size and divide it by 140 (possible average length of one line)\r
235         int lines = Math.round(this.length() / 140 + 0.5f);\r
236 \r
237         // Debug message\r
238         this.getLogger().debug(MessageFormat.format("lines={0}", lines));\r
239 \r
240         // Instance list\r
241         // @TODO The maximum length could be guessed from file size?\r
242         List<Contact> list = new ArrayList<>(lines);\r
243 \r
244         // Init variables\r
245         StringTokenizer tokenizer;\r
246         String line;\r
247 \r
248         // Read all lines\r
249         while (!this.isEndOfFile()) {\r
250             // Then read a line\r
251             line = this.readLine();\r
252 \r
253             // Debug message\r
254             this.getLogger().debug(MessageFormat.format("line={0}", line));\r
255 \r
256             // Then tokenize it\r
257             // @TODO Move this into separate method\r
258             tokenizer = new StringTokenizer(line, ";");\r
259 \r
260             // Count round\r
261             int count = 0;\r
262 \r
263             // Init contact object\r
264             Contact contact = null;\r
265 \r
266             // The tokens are now available, so get all\r
267             while (tokenizer.hasMoreElements()) {\r
268                 // Get next token\r
269                 String token = tokenizer.nextToken();\r
270 \r
271                 // Debug message\r
272                 this.getLogger().debug(MessageFormat.format("token={0}", token));\r
273 \r
274                 // Verify token, it must have double-quotes on each side\r
275                 if ((!token.startsWith("\"")) || (!token.endsWith("\""))) {\r
276                     // Something bad was read\r
277                     throw new BadTokenException(MessageFormat.format("Token {0} has not double-quotes on both ends.", token));\r
278                 }\r
279 \r
280                 // All fine, so remove it\r
281                 String strippedToken = token.substring(1, token.length() - 1);\r
282 \r
283                 // Is the string's content "null"?\r
284                 if (strippedToken.equals("null")) {\r
285                     // Debug message\r
286                     this.getLogger().debug(MessageFormat.format("strippedToken={0} - NULL!", strippedToken));\r
287 \r
288                     // This needs to be set to null\r
289                     strippedToken = null;\r
290                 }\r
291 \r
292                 // Debug message\r
293                 this.getLogger().debug(MessageFormat.format("strippedToken={0}", strippedToken));\r
294 \r
295                 // Init number/string data values\r
296                 String strData = strippedToken;\r
297                 Long num = null;\r
298                 Boolean bool = null;\r
299                 char gender = '?';\r
300                 \r
301                 // Now, let's try a number check, if no null\r
302                 if (strippedToken != null) {\r
303                     // Okay, no null, maybe the string bears a decimal number?\r
304                     try {\r
305                         num = Long.valueOf(strippedToken);\r
306 \r
307                         // Debug message\r
308                         this.getLogger().debug(MessageFormat.format("strippedToken={0} - NUMBER!", strippedToken));\r
309                     } catch (final NumberFormatException ex) {\r
310                         // No number, then set default\r
311                         num = null;\r
312                     }\r
313                 }\r
314                 \r
315                 // Now, let's try a boolean check, if no null\r
316                 if ((strippedToken != null) && (num == null) && ((strippedToken.equals("true")) || (strippedToken.equals("false")))) {\r
317                     // Debug message\r
318                     this.getLogger().debug(MessageFormat.format("strippedToken={0} - BOOLEAN!", strippedToken));\r
319 \r
320                     // parseBoolean() is relaxed, so no exceptions\r
321                     bool = Boolean.valueOf(strippedToken);\r
322                 }\r
323                 \r
324                 // Now, let's try a boolean check, if no null\r
325                 if ((strippedToken != null) && (num == null) && (bool == null) && ((strippedToken.equals("M")) || (strippedToken.equals("F")) || (strippedToken.equals("C")))) {\r
326                     // Get first character\r
327                     gender = strippedToken.charAt(0);\r
328                 }\r
329 \r
330                 // Now it depends on the counter which position we need to check\r
331                 switch (count) {\r
332                     case 0: // isOwnContact\r
333                         assert((bool instanceof Boolean));\r
334 \r
335                         // Debug message\r
336                         this.getLogger().debug(MessageFormat.format("bool={0}", bool));\r
337 \r
338                         // Is it own contact?\r
339                         if (true == bool) {\r
340                             // Debug message\r
341                             this.getLogger().debug("Creating UserContact object ...");\r
342 \r
343                             // Own entry\r
344                             contact = new UserContact();\r
345                         } else {\r
346                             // Debug message\r
347                             this.getLogger().debug("Creating BookContact object ...");\r
348 \r
349                             // Other contact\r
350                             contact = new BookContact();\r
351                         }\r
352                         break;\r
353 \r
354                     case 1: // Gender\r
355                         assert(contact instanceof Contact) : "First token was not boolean";\r
356                         assert(gender != '?') : "Gender is not detected.";\r
357 \r
358                         // Update data\r
359                         contact.updateNameData(gender, null, null, null);\r
360                         break;\r
361 \r
362                     case 2: // Surname\r
363                         assert(contact instanceof Contact) : "First token was not boolean";\r
364                         assert(gender != '?') : "Gender is not detected.";\r
365 \r
366                         // Update data\r
367                         contact.updateNameData(gender, strippedToken, null, null);\r
368                         break;\r
369 \r
370                     case 3: // Family name\r
371                         assert(contact instanceof Contact) : "First token was not boolean";\r
372                         assert(gender != '?') : "Gender is not detected.";\r
373 \r
374                         // Update data\r
375                         contact.updateNameData(gender, null, strippedToken, null);\r
376                         break;\r
377 \r
378                     case 4: // Company name\r
379                         assert(contact instanceof Contact) : "First token was not boolean";\r
380                         assert(gender != '?') : "Gender is not detected.";\r
381 \r
382                         // Update data\r
383                         contact.updateNameData(gender, null, null, strippedToken);\r
384                         break;\r
385 \r
386                     case 5: // Street number\r
387                         assert(contact instanceof Contact) : "First token was not boolean";\r
388 \r
389                         // Update data\r
390                         contact.updateAddressData(strippedToken, 0, null, null);\r
391                         break;\r
392 \r
393                     case 6: // ZIP code\r
394                         assert(contact instanceof Contact) : "First token was not boolean";\r
395 \r
396                         // Update data\r
397                         contact.updateAddressData(null, num, null, null);\r
398                         break;\r
399 \r
400                     case 7: // City name\r
401                         assert(contact instanceof Contact) : "First token was not boolean";\r
402 \r
403                         // Update data\r
404                         contact.updateAddressData(null, 0, strippedToken, null);\r
405                         break;\r
406 \r
407                     case 8: // Country code\r
408                         assert(contact instanceof Contact) : "First token was not boolean";\r
409 \r
410                         // Update data\r
411                         contact.updateAddressData(null, 0, null, strippedToken);\r
412                         break;\r
413 \r
414                     case 9: // Phone number\r
415                         assert(contact instanceof Contact) : "First token was not boolean";\r
416 \r
417                         // Update data\r
418                         contact.updateOtherData(strippedToken, null, null, null, null, null);\r
419                         break;\r
420 \r
421                     case 10: // Fax number\r
422                         assert(contact instanceof Contact) : "First token was not boolean";\r
423 \r
424                         // Update data\r
425                         contact.updateOtherData(null, strippedToken, null, null, null, null);\r
426                         break;\r
427 \r
428                     case 11: // Cellphone number\r
429                         assert(contact instanceof Contact) : "First token was not boolean";\r
430 \r
431                         // Update data\r
432                         contact.updateOtherData(null, null, strippedToken, null, null, null);\r
433                         break;\r
434 \r
435                     case 12: // Email address\r
436                         assert(contact instanceof Contact) : "First token was not boolean";\r
437 \r
438                         // Update data\r
439                         contact.updateOtherData(null, null, null, strippedToken, null, null);\r
440                         break;\r
441 \r
442                     case 13: // Birthday\r
443                         assert(contact instanceof Contact) : "First token was not boolean";\r
444 \r
445                         // Update data\r
446                         contact.updateOtherData(null, null, null, null, strippedToken, null);\r
447                         break;\r
448 \r
449                     case 14: // Birthday\r
450                         assert(contact instanceof Contact) : "First token was not boolean";\r
451 \r
452                         // Update data\r
453                         contact.updateOtherData(null, null, null, null, null, strippedToken);\r
454                         break;\r
455 \r
456                     default: // New data entry\r
457                         this.getLogger().warn(MessageFormat.format("Will not handle unknown data {0} at index {1}", strippedToken, count));\r
458                         break;\r
459                 }\r
460 \r
461                 // Increment counter for next round\r
462                 count++;\r
463             }\r
464 \r
465             // Add contact\r
466             this.addContactToList(contact, list);\r
467         }\r
468 \r
469         // Return finished list\r
470         this.getLogger().trace(MessageFormat.format("list.size()={0} : EXIT!", list.size()));\r
471         return list;\r
472     }\r
473 \r
474     /**\r
475      * Reads a line from file base\r
476      *\r
477      * @return Read line from file\r
478      */\r
479     private String readLine () {\r
480         // Init input\r
481         String input = null;\r
482 \r
483         try {\r
484             input = this.storageFile.readLine();\r
485         } catch (final IOException ex) {\r
486             this.getLogger().catching(ex);\r
487         }\r
488 \r
489         // Return read string or null\r
490         return input;\r
491     }\r
492 }\r