Source code: org/hsqldb/TextCache.java
1 /* Copyright (c) 2001-2002, 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;
33
34 import java.io.IOException;
35 import java.io.File;
36 import java.sql.SQLException;
37
38 /**
39 * Handles operations on a DatabaseFile object and uses signle
40 * TextDdatbaseRowInput and TextDatabaseRowOutput objects to read and write
41 * rows of data to the file in text table format.
42 *
43 * @author sqlbob@users (RMP)
44 * @version 1.7.0
45 */
46 class TextCache extends org.hsqldb.Cache {
47
48 public static final String NL = System.getProperty("line.separator");
49 private String fs;
50 private String vs;
51 private String lvs;
52 private DatabaseRowOutputInterface out;
53 protected boolean readOnly;
54 protected TextDatabaseRowInput in;
55 protected boolean ignoreFirst;
56 protected String ignoredFirst = NL;
57
58 private class TextSource {
59
60 private String source;
61
62 TextSource(String s) {
63 source = s;
64 }
65
66 String getAttr(String id, String ret) {
67
68 id = ";" + id + "=";
69
70 int len = id.length();
71 int start;
72 int end;
73
74 if ((start = source.indexOf(id)) != -1) {
75 start += len;
76
77 if ((end = source.indexOf(";", start)) == -1) {
78 end = source.length();
79 }
80
81 ret = source.substring(start, end);
82 source = source.substring(0, start - len)
83 + source.substring(end);
84 }
85
86 return (ret);
87 }
88 }
89
90 /**
91 * TextCache constructor declaration <P>
92 *
93 * The cache constructor sets up the initial parameters of the cache
94 * object, setting the name used for the file, etc.
95 *
96 * @param name of database file
97 * @param propPrefix prefix for relevant properties
98 * @param props Description of the Parameter
99 * @exception SQLException Description of the Exception
100 */
101 TextCache(String name, String propPrefix,
102 HsqlDatabaseProperties props) throws SQLException {
103
104 super("", props);
105
106 TextSource textSource = new TextSource(name);
107
108 //-- Get separators:
109 fs = translateSep(textSource.getAttr("fs", null));
110 vs = textSource.getAttr("vs", fs);
111
112 if (vs != fs) {
113 vs = translateSep(vs);
114 }
115
116 String lvs = textSource.getAttr("lvs", fs);
117
118 if (lvs != fs) {
119 lvs = translateSep(lvs);
120 }
121
122 if (fs == null) {
123 fs = translateSep(props.getProperty(propPrefix + "fs"), true);
124
125 if (fs == null) {
126 fs = ",";
127 }
128 }
129
130 if (vs == null) {
131 vs = props.getProperty(propPrefix + "vs", fs);
132
133 if (vs != fs) {
134 vs = translateSep(vs, true);
135 }
136 }
137
138 if (lvs == null) {
139 lvs = props.getProperty(propPrefix + "lvs", fs);
140
141 if (lvs != fs) {
142 lvs = translateSep(lvs, true);
143 }
144 }
145
146 //-- Get boolean settings:
147 String skipFirst = textSource.getAttr("ignore_first", null);
148
149 if (skipFirst == null) {
150 skipFirst = props.getProperty(propPrefix + "ignore_first",
151 "false");
152 }
153
154 ignoreFirst = skipFirst.equals("true");
155
156 String quoted = textSource.getAttr("quoted", null);
157
158 if (quoted == null) {
159 quoted = props.getProperty(propPrefix + "quoted", "true");
160 }
161
162 String emptyIsNull = textSource.getAttr("empty_is_null", null);
163
164 if (emptyIsNull == null) {
165 emptyIsNull = props.getProperty(propPrefix + "empty_is_null",
166 "true");
167 }
168
169 // Get file name
170 sName = textSource.source;
171
172 if (sName.endsWith(";")) {
173 sName = sName.substring(0, sName.length() - 1);
174 }
175
176 try {
177 if (quoted.equals("true")) {
178 in = new QuotedTextDatabaseRowInput(
179 fs, vs, lvs, emptyIsNull.equals("true"));
180 out = new QuotedTextDatabaseRowOutput(fs, vs, lvs);
181 } else {
182 in = new TextDatabaseRowInput(fs, vs, lvs,
183 emptyIsNull.equals("true"));
184 out = new TextDatabaseRowOutput(fs, vs, lvs);
185 }
186 } catch (IOException e) {
187 throw (Trace.error(Trace.FILE_IO_ERROR,
188 "invalid separator(s):" + e));
189 }
190 }
191
192 private String translateSep(String sep) {
193 return (translateSep(sep, false));
194 }
195
196 private String translateSep(String sep, boolean isProperty) {
197
198 if (sep == null) {
199 return (null);
200 }
201
202 int next = 0;
203
204 if ((next = sep.indexOf('\\')) != -1) {
205 int start = 0;
206 char sepArray[] = sep.toCharArray();
207 char ch = 0;
208 int len = sep.length();
209 StringBuffer realSep = new StringBuffer(len);
210
211 do {
212 realSep.append(sepArray, start, next - start);
213
214 start = ++next;
215
216 if (next >= len) {
217 realSep.append('\\');
218
219 break;
220 }
221
222 if (!isProperty) {
223 ch = sepArray[next];
224 }
225
226 if (ch == 'n') {
227 realSep.append('\n');
228
229 start++;
230 } else if (ch == 'r') {
231 realSep.append('\r');
232
233 start++;
234 } else if (ch == 't') {
235 realSep.append('\t');
236
237 start++;
238 } else if (ch == '\\') {
239 realSep.append('\\');
240
241 start++;
242 } else if (ch == 'u') {
243 start++;
244
245 realSep.append(
246 (char) Integer.parseInt(
247 sep.substring(start, start + 4), 16));
248
249 start += 4;
250 } else if (sep.startsWith("semi", next)) {
251 realSep.append(';');
252
253 start += 4;
254 } else if (sep.startsWith("space", next)) {
255 realSep.append(' ');
256
257 start += 5;
258 } else if (sep.startsWith("quote", next)) {
259 realSep.append('\"');
260
261 start += 5;
262 } else if (sep.startsWith("apos", next)) {
263 realSep.append('\'');
264
265 start += 4;
266 } else {
267 realSep.append('\\');
268 realSep.append(sepArray[next]);
269
270 start++;
271 }
272 } while ((next = sep.indexOf('\\', start)) != -1);
273
274 realSep.append(sepArray, start, len - start);
275
276 sep = realSep.toString();
277 }
278
279 return (sep);
280 }
281
282 /**
283 * open method declaration <P>
284 *
285 * The open method creates or opens a database file.
286 *
287 * @param readonly Description of the Parameter
288 * @param ignore1st Description of the Parameter
289 * @throws SQLException
290 */
291 void open(boolean readonly) throws SQLException {
292
293 try {
294 rFile = new DatabaseFile(sName, (readonly) ? "r"
295 : "rw", 4096);
296 iFreePos = (int) rFile.length();
297
298 if ((iFreePos == 0) && ignoreFirst) {
299 rFile.write(ignoredFirst.getBytes());
300
301 iFreePos = ignoredFirst.length();
302 }
303 } catch (Exception e) {
304 throw Trace.error(Trace.FILE_IO_ERROR,
305 "error " + e + " opening " + sName);
306 }
307
308 readOnly = readonly;
309 }
310
311 void reopen() throws SQLException {
312 open(readOnly);
313 in.reset();
314 }
315
316 /**
317 * flush method declaration <P>
318 *
319 * The flush method saves all cached data to the file, saves the free
320 * position and closes the file.
321 *
322 * @throws SQLException
323 */
324 void flush() throws SQLException {
325
326 if (rFile == null) {
327 return;
328 }
329
330 try {
331 rFile.seek(0);
332 saveAll();
333
334 boolean empty = (rFile.length() <= NL.length());
335
336 rFile.close();
337
338 rFile = null;
339
340 if (empty &&!readOnly) {
341 new File(sName).delete();
342 }
343 } catch (Exception e) {
344 throw Trace.error(Trace.FILE_IO_ERROR,
345 "error " + e + " closing " + sName);
346 }
347 }
348
349 void purge() throws SQLException {
350
351 if (rFile == null) {
352 return;
353 }
354
355 try {
356 if (readOnly) {
357 flush();
358 } else {
359 rFile.close();
360
361 rFile = null;
362
363 new File(sName).delete();
364 }
365 } catch (Exception e) {
366 throw Trace.error(Trace.FILE_IO_ERROR,
367 "error " + e + " purging " + sName);
368 }
369 }
370
371 void free(CachedRow r) throws SQLException {
372
373 int pos = r.iPos;
374 int length = r.storageSize;
375
376 //-- Change to blank line:
377 StringBuffer blank = new StringBuffer(length);
378
379 length -= NL.length();
380
381 for (int i = 0; i < length; i++) {
382 blank.append(' ');
383 }
384
385 blank.append(NL);
386
387 try {
388 rFile.seek(pos);
389 rFile.writeBytes(blank.toString());
390 } catch (IOException e) {
391 throw (Trace.error(Trace.FILE_IO_ERROR, e + ""));
392 }
393
394 remove(r);
395 }
396
397 protected void setStorageSize(CachedRow r) throws SQLException {
398
399 int size;
400
401 try {
402 out.writeData(r.getData(), r.getTable());
403
404 size = out.toByteArray().length;
405 } catch (IOException e) {
406 throw (Trace.error(Trace.FILE_IO_ERROR, e + ""));
407 }
408
409 r.storageSize = size;
410 }
411
412 protected CachedRow makeRow(int pos, Table t) throws SQLException {
413
414 CachedRow r = null;
415
416 try {
417 StringBuffer buffer = new StringBuffer(80);
418 boolean blank = true;
419 boolean complete = false;
420
421 try {
422 char c;
423 int next;
424
425 rFile.readSeek(pos);
426
427 //-- The following should work for DOS, MAC, and Unix line
428 //-- separators regardless of host OS.
429 while (true) {
430 next = rFile.read();
431
432 if (next == -1) {
433 break;
434 }
435
436 c = (char) (next & 0xff);
437
438 //-- Ensure line is complete.
439 if (c == '\n') {
440 buffer.append('\n');
441
442 //-- Store first line.
443 if (ignoreFirst && pos == 0) {
444 ignoredFirst = buffer.toString();
445 blank = true;
446 }
447
448 //-- Ignore blanks
449 if (!blank) {
450 complete = true;
451
452 break;
453 } else {
454 pos += buffer.length();
455
456 buffer.setLength(0);
457
458 blank = true;
459
460 in.skippedLine();
461
462 continue;
463 }
464 }
465
466 if (c == '\r') {
467
468 //-- Check for newline
469 try {
470 next = rFile.read();
471
472 if (next == -1) {
473 break;
474 }
475
476 c = (char) (next & 0xff);
477
478 if (c == '\n') {
479 buffer.append('\n');
480 }
481 } catch (Exception e2) {
482 ;
483 }
484
485 buffer.append('\n');
486
487 //-- Store first line.
488 if (ignoreFirst && pos == 0) {
489 ignoredFirst = buffer.toString();
490 blank = true;
491 }
492
493 //-- Ignore blanks.
494 if (!blank) {
495 complete = true;
496
497 break;
498 } else {
499 pos += buffer.length();
500
501 buffer.setLength(0);
502
503 blank = true;
504
505 in.skippedLine();
506
507 continue;
508 }
509 }
510
511 if (c != ' ') {
512 blank = false;
513 }
514
515 buffer.append(c);
516 }
517 } catch (Exception e) {
518 complete = false;
519 }
520
521 if (complete) {
522 in.setSource(buffer.toString(), pos);
523
524 r = new CachedRow(t, in);
525 }
526 } catch (Exception e) {
527 e.printStackTrace();
528
529 throw Trace.error(Trace.FILE_IO_ERROR, "reading: " + e);
530 }
531
532 return (r);
533 }
534
535 protected void saveRow(CachedRow r) throws IOException, SQLException {
536
537 rFile.seek(r.iPos);
538 r.write(out);
539 rFile.write(out.toByteArray());
540 }
541 }