KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > hsqldb > persist > TextCache


1 /* Copyright (c) 2001-2005, The HSQL Development Group
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  * Redistributions of source code must retain the above copyright notice, this
8  * list of conditions and the following disclaimer.
9  *
10  * Redistributions in binary form must reproduce the above copyright notice,
11  * this list of conditions and the following disclaimer in the documentation
12  * and/or other materials provided with the distribution.
13  *
14  * Neither the name of the HSQL Development Group nor the names of its
15  * contributors may be used to endorse or promote products derived from this
16  * software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
22  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */

30
31
32 package org.hsqldb.persist;
33
34 import java.io.IOException JavaDoc;
35 import java.io.UnsupportedEncodingException JavaDoc;
36
37 import org.hsqldb.HsqlException;
38 import org.hsqldb.Table;
39 import org.hsqldb.Trace;
40 import org.hsqldb.lib.FileUtil;
41 import org.hsqldb.lib.HsqlByteArrayOutputStream;
42 import org.hsqldb.rowio.RowInputInterface;
43 import org.hsqldb.rowio.RowInputText;
44 import org.hsqldb.rowio.RowInputTextQuoted;
45 import org.hsqldb.rowio.RowOutputText;
46 import org.hsqldb.rowio.RowOutputTextQuoted;
47 import org.hsqldb.scriptio.ScriptWriterText;
48 import org.hsqldb.store.ObjectCacheHashMap;
49 import org.hsqldb.Database;
50
51 // Ito Kazumitsu 20030328 - patch 1.7.2 - character encoding support
52
// Dimitri Maziuk - patch for NL in string support
53
// sqlbob@users - updated for 1.8.0 to allow new-lines in fields
54
// fredt@users - updated for 1.8.0 to allow correct behaviour with transactions
55

56 /**
57  * Acts as a buffer manager for a single TEXT table with respect its Row data.<p>
58  *
59  * Handles read/write operations on the table's text format data file using a
60  * compatible pair of org.hsqldb.rowio input/output class instances.
61  *
62  *
63  * @author sqlbob@users (RMP)
64  * @version 1.8.0
65  * @since 1.7.0
66  */

67 public class TextCache extends DataFileCache {
68
69     //state of Cache
70
public static final String JavaDoc NL = System.getProperty("line.separator");
71     public String JavaDoc fs;
72     public String JavaDoc vs;
73     public String JavaDoc lvs;
74     public String JavaDoc stringEncoding;
75     public boolean isQuoted;
76     public boolean isAllQuoted;
77     public boolean ignoreFirst;
78     protected String JavaDoc header;
79     protected Table table;
80     private ObjectCacheHashMap uncommittedCache;
81
82     //
83
final static char DOUBLE_QUOTE_CHAR = '\"';
84     final static char BACKSLASH_CHAR = '\\';
85     final static char LF_CHAR = '\n';
86     final static char CR_CHAR = '\r';
87
88     /**
89      * The source string for a cached table is evaluated and the parameters
90      * are used to open the source file.<p>
91      *
92      * Settings are used in this order: (1) settings specified in the
93      * source string for the table (2) global database settings in
94      * *.properties file (3) program defaults
95      *
96      * fredt - this used to write rows as soon as they are inserted
97      * but now this is subject to session autoCommit / or commit
98      * storeOnInsert = true;
99      */

100     TextCache(Table table, String JavaDoc name) throws HsqlException {
101
102         super(table.database, name);
103
104         this.table = table;
105         uncommittedCache = new ObjectCacheHashMap(5);
106     }
107
108     protected void initParams(Database database,
109                               String JavaDoc baseFileName) throws HsqlException {
110
111         fileName = baseFileName;
112         this.database = database;
113         fa = FileUtil.getDefaultInstance();
114
115         HsqlProperties tableprops =
116             HsqlProperties.delimitedArgPairsToProps(fileName, "=", ";", null);
117
118         //-- Get file name
119
switch (tableprops.errorCodes.length) {
120
121             case 0 :
122                 throw Trace.error(Trace.TEXT_TABLE_SOURCE,
123                                   Trace.TEXT_TABLE_SOURCE_FILENAME);
124             case 1 :
125
126                 // source file name is the only key without a value
127
fileName = tableprops.errorKeys[0].trim();
128                 break;
129
130             default :
131                 throw Trace.error(Trace.TEXT_TABLE_SOURCE,
132                                   Trace.TEXT_TABLE_SOURCE_VALUE_MISSING,
133                                   tableprops.errorKeys[1]);
134         }
135
136         //-- Get separators:
137
HsqlDatabaseProperties dbProps = database.getProperties();
138
139         fs = translateSep(tableprops.getProperty("fs",
140                 dbProps.getProperty(HsqlDatabaseProperties.textdb_fs, ",")));
141         vs = translateSep(tableprops.getProperty("vs",
142                 dbProps.getProperty(HsqlDatabaseProperties.textdb_vs, fs)));
143         lvs = translateSep(tableprops.getProperty("lvs",
144                 dbProps.getProperty(HsqlDatabaseProperties.textdb_lvs, fs)));
145
146         if (fs.length() == 0 || vs.length() == 0 || lvs.length() == 0) {
147             throw Trace.error(Trace.TEXT_TABLE_SOURCE,
148                               Trace.TEXT_TABLE_SOURCE_SEPARATOR);
149         }
150
151         //-- Get booleans
152
ignoreFirst = tableprops.isPropertyTrue(
153             "ignore_first",
154             dbProps.isPropertyTrue(
155                 HsqlDatabaseProperties.textdb_ignore_first, false));
156         isQuoted = tableprops.isPropertyTrue(
157             "quoted",
158             dbProps.isPropertyTrue(
159                 HsqlDatabaseProperties.textdb_quoted, true));
160         isAllQuoted = tableprops.isPropertyTrue(
161             "all_quoted",
162             dbProps.isPropertyTrue(
163                 HsqlDatabaseProperties.textdb_all_quoted, false));
164
165         //-- Get encoding
166
stringEncoding = translateSep(tableprops.getProperty("encoding",
167                 dbProps.getProperty(HsqlDatabaseProperties.textdb_encoding,
168                                     "ASCII")));
169
170         //-- Get size and scale
171
int cacheScale = tableprops.getIntegerProperty(
172             "cache_scale",
173             dbProps.getIntegerProperty(
174                 HsqlDatabaseProperties.textdb_cache_scale, 10, 8, 16));
175         int cacheSizeScale = tableprops.getIntegerProperty(
176             "cache_size_scale",
177             dbProps.getIntegerProperty(
178                 HsqlDatabaseProperties.textdb_cache_size_scale, 10, 8, 20));
179         int lookupTableLength = 1 << cacheScale;
180         int avgRowBytes = 1 << cacheSizeScale;
181
182         maxCacheSize = lookupTableLength * 3;
183         maxCacheBytes = maxCacheSize * avgRowBytes;
184         maxDataFileSize = Integer.MAX_VALUE;
185         cachedRowPadding = 1;
186         cacheFileScale = 1;
187     }
188
189     protected void initBuffers() {
190
191         if (isQuoted || isAllQuoted) {
192             rowIn = new RowInputTextQuoted(fs, vs, lvs, isAllQuoted);
193             rowOut = new RowOutputTextQuoted(fs, vs, lvs, isAllQuoted,
194                                              stringEncoding);
195         } else {
196             rowIn = new RowInputText(fs, vs, lvs, false);
197             rowOut = new RowOutputText(fs, vs, lvs, false, stringEncoding);
198         }
199     }
200
201     private String JavaDoc translateSep(String JavaDoc sep) {
202         return translateSep(sep, false);
203     }
204
205     /**
206      * Translates the escaped characters in a separator string and returns
207      * the non-escaped string.
208      */

209     private String JavaDoc translateSep(String JavaDoc sep, boolean isProperty) {
210
211         if (sep == null) {
212             return (null);
213         }
214
215         int next = 0;
216
217         if ((next = sep.indexOf(BACKSLASH_CHAR)) != -1) {
218             int start = 0;
219             char[] sepArray = sep.toCharArray();
220             char ch = 0;
221             int len = sep.length();
222             StringBuffer JavaDoc realSep = new StringBuffer JavaDoc(len);
223
224             do {
225                 realSep.append(sepArray, start, next - start);
226
227                 start = ++next;
228
229                 if (next >= len) {
230                     realSep.append(BACKSLASH_CHAR);
231
232                     break;
233                 }
234
235                 if (!isProperty) {
236                     ch = sepArray[next];
237                 }
238
239                 if (ch == 'n') {
240                     realSep.append(LF_CHAR);
241
242                     start++;
243                 } else if (ch == 'r') {
244                     realSep.append(CR_CHAR);
245
246                     start++;
247                 } else if (ch == 't') {
248                     realSep.append('\t');
249
250                     start++;
251                 } else if (ch == BACKSLASH_CHAR) {
252                     realSep.append(BACKSLASH_CHAR);
253
254                     start++;
255                 } else if (ch == 'u') {
256                     start++;
257
258                     realSep.append(
259                         (char) Integer.parseInt(
260                             sep.substring(start, start + 4), 16));
261
262                     start += 4;
263                 } else if (sep.startsWith("semi", next)) {
264                     realSep.append(';');
265
266                     start += 4;
267                 } else if (sep.startsWith("space", next)) {
268                     realSep.append(' ');
269
270                     start += 5;
271                 } else if (sep.startsWith("quote", next)) {
272                     realSep.append(DOUBLE_QUOTE_CHAR);
273
274                     start += 5;
275                 } else if (sep.startsWith("apos", next)) {
276                     realSep.append('\'');
277
278                     start += 4;
279                 } else {
280                     realSep.append(BACKSLASH_CHAR);
281                     realSep.append(sepArray[next]);
282
283                     start++;
284                 }
285             } while ((next = sep.indexOf(BACKSLASH_CHAR, start)) != -1);
286
287             realSep.append(sepArray, start, len - start);
288
289             sep = realSep.toString();
290         }
291
292         return sep;
293     }
294
295     /**
296      * Opens a data source file.
297      */

298     public void open(boolean readonly) throws HsqlException {
299
300         fileFreePosition = 0;
301
302         try {
303             dataFile = ScaledRAFile.newScaledRAFile(database, fileName,
304                     readonly, ScaledRAFile.DATA_FILE_RAF, null, null);
305             fileFreePosition = dataFile.length();
306
307             if (fileFreePosition > Integer.MAX_VALUE) {
308                 throw new IOException JavaDoc();
309             }
310
311             initBuffers();
312         } catch (Exception JavaDoc e) {
313             throw Trace.error(Trace.FILE_IO_ERROR,
314                               Trace.TextCache_openning_file_error,
315                               new Object JavaDoc[] {
316                 fileName, e
317             });
318         }
319
320         cacheReadonly = readonly;
321     }
322
323     void reopen() throws HsqlException {
324         open(cacheReadonly);
325     }
326
327     /**
328      * Writes newly created rows to disk. In the current implentation,
329      * such rows have already been saved, so this method just removes a
330      * source file that has no rows.
331      */

332     public void close(boolean write) throws HsqlException {
333
334         if (dataFile == null) {
335             return;
336         }
337
338         try {
339             cache.saveAll();
340
341             boolean empty = (dataFile.length() <= NL.length());
342
343             dataFile.close();
344
345             dataFile = null;
346
347             if (empty &&!cacheReadonly) {
348                 FileUtil.delete(fileName);
349             }
350         } catch (Exception JavaDoc e) {
351             throw Trace.error(Trace.FILE_IO_ERROR,
352                               Trace.TextCache_closing_file_error,
353                               new Object JavaDoc[] {
354                 fileName, e
355             });
356         }
357     }
358
359     /**
360      * Closes the source file and deletes it if it is not read-only.
361      */

362     void purge() throws HsqlException {
363
364         uncommittedCache.clear();
365
366         try {
367             if (cacheReadonly) {
368                 close(false);
369             } else {
370                 if (dataFile != null) {
371                     dataFile.close();
372
373                     dataFile = null;
374                 }
375
376                 FileUtil.delete(fileName);
377             }
378         } catch (Exception JavaDoc e) {
379             throw Trace.error(Trace.FILE_IO_ERROR,
380                               Trace.TextCache_purging_file_error,
381                               new Object JavaDoc[] {
382                 fileName, e
383             });
384         }
385     }
386
387     /**
388      *
389      */

390     public synchronized void remove(int pos,
391                                     PersistentStore store)
392                                     throws IOException JavaDoc {
393
394         CachedObject row = (CachedObject) uncommittedCache.remove(pos);
395
396         if (row != null) {
397             return;
398         }
399
400         row = cache.release(pos);
401
402         clearRowImage(row);
403         release(pos);
404     }
405
406     private void clearRowImage(CachedObject row) throws IOException JavaDoc {
407
408         int length = row.getStorageSize()
409                      - ScriptWriterText.BYTES_LINE_SEP.length;
410
411         rowOut.reset();
412
413         HsqlByteArrayOutputStream out = rowOut.getOutputStream();
414
415         out.fill(' ', length);
416         out.write(ScriptWriterText.BYTES_LINE_SEP);
417         dataFile.seek(row.getPos());
418         dataFile.write(out.getBuffer(), 0, out.size());
419     }
420
421     public synchronized void removePersistence(int pos,
422             PersistentStore store) throws IOException JavaDoc {
423
424         CachedObject row = (CachedObject) uncommittedCache.get(pos);
425
426         if (row != null) {
427             return;
428         }
429
430         row = cache.get(pos);
431
432         clearRowImage(row);
433     }
434
435     protected synchronized RowInputInterface readObject(int pos)
436     throws IOException JavaDoc {
437
438         ByteArray buffer = new ByteArray(80);
439         boolean complete = false;
440         boolean wasCR = false;
441         int c;
442         boolean hasQuote = false;
443         boolean wasNormal = false;
444
445         pos = findNextUsedLinePos(pos);
446
447         if (pos == -1) {
448             return null;
449         }
450
451         dataFile.seek(pos);
452
453         while (!complete) {
454             wasNormal = false;
455             c = dataFile.read();
456
457             if (c == -1) {
458                 if (buffer.length() == 0) {
459                     return null;
460                 }
461
462                 complete = true;
463
464                 if (wasCR) {
465                     break;
466                 }
467
468                 if (!cacheReadonly) {
469                     dataFile.write(ScriptWriterText.BYTES_LINE_SEP, 0,
470                                    ScriptWriterText.BYTES_LINE_SEP.length);
471                 }
472
473                 break;
474             }
475
476             switch (c) {
477
478                 case DOUBLE_QUOTE_CHAR :
479                     wasNormal = true;
480                     complete = wasCR;
481                     wasCR = false;
482                     hasQuote = !hasQuote;
483                     break;
484
485                 case CR_CHAR :
486                     wasCR = !hasQuote;
487                     break;
488
489                 case LF_CHAR :
490                     complete = !hasQuote;
491                     break;
492
493                 default :
494                     wasNormal = true;
495                     complete = wasCR;
496                     wasCR = false;
497             }
498
499             buffer.append(c);
500         }
501
502         if (complete) {
503             int length = (int) dataFile.getFilePointer() - pos;
504
505             if (wasNormal) {
506                 length--;
507             }
508
509             ((RowInputText) rowIn).setSource(buffer.toString(), pos, length);
510
511             return rowIn;
512         }
513
514         return null;
515     }
516
517     public int readHeaderLine() throws HsqlException {
518
519         boolean complete = false;
520         boolean wasCR = false;
521         boolean wasNormal = false;
522         ByteArray buffer = new ByteArray(80);
523
524         while (!complete) {
525             wasNormal = false;
526
527             int c;
528
529             try {
530                 c = dataFile.read();
531
532                 if (c == -1) {
533                     if (buffer.length() == 0) {
534                         return 0;
535                     }
536
537                     complete = true;
538
539                     if (!cacheReadonly) {
540                         dataFile.write(
541                             ScriptWriterText.BYTES_LINE_SEP, 0,
542                             ScriptWriterText.BYTES_LINE_SEP.length);
543                     }
544
545                     break;
546                 }
547             } catch (IOException JavaDoc e) {
548                 throw Trace.error(Trace.TEXT_FILE);
549             }
550
551             switch (c) {
552
553                 case CR_CHAR :
554                     wasCR = true;
555                     break;
556
557                 case LF_CHAR :
558                     complete = true;
559                     break;
560
561                 default :
562                     wasNormal = true;
563                     complete = wasCR;
564                     wasCR = false;
565             }
566
567             buffer.append(c);
568         }
569
570         header = buffer.toString();
571
572         try {
573             int length = (int) dataFile.getFilePointer();
574
575             if (wasNormal) {
576                 length--;
577             }
578
579             return length;
580         } catch (IOException JavaDoc e) {
581             throw Trace.error(Trace.TEXT_FILE);
582         }
583     }
584
585     // fredt - new method
586

587     /**
588      * Searches from file pointer, pos, and finds the beginning of the first
589      * line that contains any non-space character. Increments the row counter
590      * when a blank line is skipped.
591      *
592      * If none found return -1
593      */

594     int findNextUsedLinePos(int pos) throws IOException JavaDoc {
595
596         int firstPos = pos;
597         int currentPos = pos;
598         boolean wasCR = false;
599
600         dataFile.seek(pos);
601
602         while (true) {
603             int c = dataFile.read();
604
605             currentPos++;
606
607             switch (c) {
608
609                 case CR_CHAR :
610                     wasCR = true;
611                     break;
612
613                 case LF_CHAR :
614                     wasCR = false;
615
616                     ((RowInputText) rowIn).skippedLine();
617
618                     firstPos = currentPos;
619                     break;
620
621                 case ' ' :
622                     if (wasCR) {
623                         wasCR = false;
624
625                         ((RowInputText) rowIn).skippedLine();
626                     }
627                     break;
628
629                 case -1 :
630                     return -1;
631
632                 default :
633                     return firstPos;
634             }
635         }
636     }
637
638     public synchronized void add(CachedObject object) throws IOException JavaDoc {
639         super.add(object);
640         clearRowImage(object);
641     }
642
643     public synchronized CachedObject get(int i, PersistentStore store,
644                                          boolean keep) throws HsqlException {
645
646         if (i < 0) {
647             return null;
648         }
649
650         CachedObject o = (CachedObject) uncommittedCache.get(i);
651
652         if (o == null) {
653             o = super.get(i, store, keep);
654         }
655
656 /*
657         if (o == null) {
658             o = super.get(i, store, keep);
659         }
660 */

661         return o;
662     }
663
664     /**
665      * This is called internally when old rows need to be removed from the
666      * cache. Text table rows that have not been saved are those that have not
667      * been committed yet. So we don't save them but add them to the
668      * uncommitted cache until such time that they are committed or rolled
669      * back- fredt
670      */

671     protected synchronized void saveRows(CachedObject[] rows, int offset,
672                                          int count) throws IOException JavaDoc {
673
674         if (count == 0) {
675             return;
676         }
677
678         for (int i = offset; i < offset + count; i++) {
679             CachedObject r = rows[i];
680
681             uncommittedCache.put(r.getPos(), r);
682
683             rows[i] = null;
684         }
685     }
686
687     /**
688      * In case the row has been moved to the uncommittedCache, removes it.
689      * Then saves the row as normal.
690      */

691     public synchronized void saveRow(CachedObject row) throws IOException JavaDoc {
692         uncommittedCache.remove(row.getPos());
693         super.saveRow(row);
694     }
695
696     public String JavaDoc getHeader() {
697         return header;
698     }
699
700     public void setHeader(String JavaDoc header) throws HsqlException {
701
702         if (ignoreFirst && fileFreePosition == 0) {
703             try {
704                 writeHeader(header);
705
706                 this.header = header;
707             } catch (IOException JavaDoc e) {
708                 throw new HsqlException(
709                     e, Trace.getMessage(Trace.GENERAL_IO_ERROR),
710                     Trace.GENERAL_IO_ERROR);
711             }
712
713             return;
714         }
715
716         throw Trace.error(Trace.TEXT_TABLE_HEADER);
717     }
718
719     private void writeHeader(String JavaDoc header) throws IOException JavaDoc {
720
721         byte[] buf = null;
722         String JavaDoc firstLine = header + NL;
723
724         try {
725             buf = firstLine.getBytes(stringEncoding);
726         } catch (UnsupportedEncodingException JavaDoc e) {
727             buf = firstLine.getBytes();
728         }
729
730         dataFile.write(buf, 0, buf.length);
731
732         fileFreePosition = buf.length;
733     }
734
735     private class ByteArray {
736
737         private byte[] buffer;
738         private int buflen;
739
740         public ByteArray(int n) {
741             buffer = new byte[n];
742             buflen = 0;
743         }
744
745         public void append(int c) {
746
747             if (buflen >= buffer.length) {
748                 byte[] newbuf = new byte[buflen + 80];
749
750                 System.arraycopy(buffer, 0, newbuf, 0, buflen);
751
752                 buffer = newbuf;
753             }
754
755             buffer[buflen] = (byte) c;
756
757             buflen++;
758         }
759
760         public int length() {
761             return buflen;
762         }
763
764         public void setLength(int l) {
765             buflen = l;
766         }
767
768         public String JavaDoc toString() {
769
770             try {
771                 return new String JavaDoc(buffer, 0, buflen, stringEncoding);
772             } catch (UnsupportedEncodingException JavaDoc e) {
773                 return new String JavaDoc(buffer, 0, buflen);
774             }
775         }
776     }
777
778     public int getLineNumber() {
779         return ((RowInputText) rowIn).getLineNumber();
780     }
781
782     protected void setFileModified() throws IOException JavaDoc {
783         fileModified = true;
784     }
785 }
786
Popular Tags