]> git.mxchange.org Git - addressbook-swing.git/blob - Addressbook/src/org/mxchange/addressbook/database/backend/csv/CsvDatabaseBackend.java
Continued:
[addressbook-swing.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      * Get length of underlaying file\r
98      *\r
99      * @return Length of underlaying file\r
100      */\r
101     @Override\r
102     public long length () {\r
103         long length = 0;\r
104         \r
105         try {\r
106             length = this.storageFile.length();\r
107             this.getLogger().debug(MessageFormat.format("length={0}", length));\r
108         } catch (final IOException ex) {\r
109             // Length cannot be determined\r
110             this.getLogger().catching(ex);\r
111             System.exit(1);\r
112         }\r
113         \r
114         // Return result\r
115         this.getLogger().trace(MessageFormat.format("length={0} : EXIT!", length));\r
116         return length;\r
117     }\r
118 \r
119     /**\r
120      * Rewinds backend\r
121      */\r
122     @Override\r
123     public void rewind (){\r
124         this.getLogger().trace("CALLED!");\r
125 \r
126         try {\r
127             // Rewind underlaying database file\r
128             this.storageFile.seek(0);\r
129         } catch (final IOException ex) {\r
130             this.getLogger().catching(ex);\r
131             System.exit(1);\r
132         }\r
133 \r
134         this.getLogger().trace("EXIT!");\r
135     }\r
136 \r
137     /**\r
138      * Stores given object by "visiting" it\r
139      *\r
140      * @param object An object implementing Storeable\r
141      * @throws java.io.IOException From "inner" class\r
142      */\r
143     @Override\r
144     public void store (final Storeable object) throws IOException {\r
145         // Make sure the instance is there (DataOutput flawor)\r
146         assert(this.storageFile instanceof DataOutput);\r
147 \r
148         // Try to cast it, this will fail if the interface is not implemented\r
149         StoreableCsv csv = (StoreableCsv) object;\r
150 \r
151         // Now get a string from the object that needs to be stored\r
152         String str = csv.getCsvStringFromStoreableObject();\r
153 \r
154         // Debug message\r
155         this.getLogger().debug(MessageFormat.format("str({0})={1}", str.length(), str));\r
156 \r
157         // The string is now a valid CSV string\r
158         this.storageFile.writeChars(str);\r
159     }\r
160 \r
161     /**\r
162      * Checks whether end of file has been reached\r
163      * \r
164      * @return Whether lines are left to read\r
165      */\r
166     private boolean isEndOfFile () {\r
167         // Default is EOF\r
168         boolean isEof = true;\r
169 \r
170         try {\r
171             isEof = (this.storageFile.getFilePointer() >= this.length());\r
172         } catch (final IOException ex) {\r
173             // Length cannot be determined\r
174             this.getLogger().catching(ex);\r
175         }\r
176 \r
177         // Return status\r
178         this.getLogger().trace(MessageFormat.format("isEof={0} : EXIT!", isEof));\r
179         return isEof;\r
180     }\r
181 \r
182     /**\r
183      * Reads the database file, if available, and adds all read lines into\r
184      * the list.\r
185      * \r
186      * @return A list with Contact instances\r
187      */\r
188     private List<Contact> readContactList () throws BadTokenException {\r
189         this.getLogger().trace("CALLED!");\r
190 \r
191         // First rewind\r
192         this.rewind();\r
193 \r
194         // Instance list\r
195         // @TODO The maximum length could be guessed from file size?\r
196         List<Contact> list = new ArrayList<>();\r
197 \r
198         // Init variables\r
199         StringTokenizer tokenizer;\r
200         String line;\r
201 \r
202         // Read all lines\r
203         while (!this.isEndOfFile()) {\r
204             // Then read a line\r
205             line = this.readLine();\r
206 \r
207             // Debug message\r
208             this.getLogger().debug(MessageFormat.format("line={0}", line));\r
209 \r
210             // Then tokenize it\r
211             // @TODO Move this into separate method\r
212             tokenizer = new StringTokenizer(line, ";");\r
213 \r
214             // Count round\r
215             int count = 0;\r
216 \r
217             // Init contact object\r
218             Contact contact = null;\r
219 \r
220             // The tokens are now available, so get all\r
221             while (tokenizer.hasMoreElements()) {\r
222                 // Get next token\r
223                 String token = tokenizer.nextToken();\r
224 \r
225                 // Debug message\r
226                 this.getLogger().debug(MessageFormat.format("token={0}", token));\r
227 \r
228                 // Verify token, it must have double-quotes on each side\r
229                 if ((!token.startsWith("\"")) || (!token.endsWith("\""))) {\r
230                     // Something bad was read\r
231                     throw new BadTokenException(MessageFormat.format("Token {0} has not double-quotes on both ends.", token));\r
232                 }\r
233 \r
234                 // All fine, so remove it\r
235                 String strippedToken = token.substring(1, token.length() - 1);\r
236 \r
237                 // Is the string's content "null"?\r
238                 if (strippedToken.equals("null")) {\r
239                     // Debug message\r
240                     this.getLogger().debug(MessageFormat.format("strippedToken={0} - NULL!", strippedToken));\r
241 \r
242                     // This needs to be set to null\r
243                     strippedToken = null;\r
244                 }\r
245 \r
246                 // Debug message\r
247                 this.getLogger().debug(MessageFormat.format("strippedToken={0}", strippedToken));\r
248 \r
249                 // Init number/string data values\r
250                 String strData = strippedToken;\r
251                 Long num = null;\r
252                 Boolean bool = null;\r
253                 char gender = '?';\r
254                 \r
255                 // Now, let's try a number check, if no null\r
256                 if (strippedToken != null) {\r
257                     // Okay, no null, maybe the string bears a decimal number?\r
258                     try {\r
259                         num = Long.valueOf(strippedToken);\r
260 \r
261                         // Debug message\r
262                         this.getLogger().debug(MessageFormat.format("strippedToken={0} - NUMBER!", strippedToken));\r
263                     } catch (final NumberFormatException ex) {\r
264                         // No number, then set default\r
265                         num = null;\r
266                     }\r
267                 }\r
268                 \r
269                 // Now, let's try a boolean check, if no null\r
270                 if ((strippedToken != null) && (num == null) && ((strippedToken.equals("true")) || (strippedToken.equals("false")))) {\r
271                     // Debug message\r
272                     this.getLogger().debug(MessageFormat.format("strippedToken={0} - BOOLEAN!", strippedToken));\r
273 \r
274                     // parseBoolean() is relaxed, so no exceptions\r
275                     bool = Boolean.valueOf(strippedToken);\r
276                 }\r
277                 \r
278                 // Now, let's try a boolean check, if no null\r
279                 if ((strippedToken != null) && (num == null) && (bool == null) && ((strippedToken.equals("M")) || (strippedToken.equals("F")) || (strippedToken.equals("C")))) {\r
280                     // Get first character\r
281                     gender = strippedToken.charAt(0);\r
282                 }\r
283 \r
284                 // Now it depends on the counter which position we need to check\r
285                 switch (count) {\r
286                     case 0: // isOwnContact\r
287                         assert((bool instanceof Boolean));\r
288 \r
289                         // Is it own contact?\r
290                         if (true == bool) {\r
291                             // Own entry\r
292                             contact = new UserContact();\r
293                         } else {\r
294                             // Other contact\r
295                             contact = new BookContact();\r
296                         }\r
297                         break;\r
298 \r
299                     case 1: // Gender\r
300                         assert(contact instanceof Contact) : "First token was not boolean";\r
301                         assert(gender != '?') : "Gender is not detected.";\r
302 \r
303                         // Update data\r
304                         contact.updateNameData(gender, null, null, null);\r
305                         break;\r
306 \r
307                     default: // New data entry\r
308                         this.getLogger().warn("Will not handle unknown data " + strippedToken + " at index " + count);\r
309                         break;\r
310                 }\r
311 \r
312                 // Increment counter for next round\r
313                 count++;\r
314             }\r
315         }\r
316 \r
317         // Return finished list\r
318         this.getLogger().trace(MessageFormat.format("list.size()={0} : EXIT!", list.size()));\r
319         return list;\r
320     }\r
321 \r
322     /**\r
323      * Reads a line from file base\r
324      *\r
325      * @return Read line from file\r
326      */\r
327     private String readLine () {\r
328         // Init input\r
329         String input = null;\r
330 \r
331         try {\r
332             input = this.storageFile.readLine();\r
333         } catch (IOException ex) {\r
334             this.getLogger().catching(ex);\r
335         }\r
336 \r
337         // Return read string or null\r
338         return input;\r
339     }\r
340 }\r