001: /* Copyright (c) 2001-2005, The HSQL Development Group
002: * All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * Redistributions of source code must retain the above copyright notice, this
008: * list of conditions and the following disclaimer.
009: *
010: * Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * Neither the name of the HSQL Development Group nor the names of its
015: * contributors may be used to endorse or promote products derived from this
016: * software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
022: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
026: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package org.hsqldb.persist;
032:
033: import java.io.EOFException;
034: import java.io.FileNotFoundException;
035: import java.io.IOException;
036: import java.io.RandomAccessFile;
037: import java.lang.reflect.Constructor;
038:
039: import org.hsqldb.Database;
040: import org.hsqldb.Trace;
041: import org.hsqldb.lib.HsqlByteArrayInputStream;
042: import org.hsqldb.lib.SimpleLog;
043: import org.hsqldb.lib.Storage;
044:
045: // fredt@users 20030111 - patch 1.7.2 by bohgammer@users - pad file before seek() beyond end
046: // some incompatible JVM implementations do not allow seek beyond the existing end of file
047:
048: /**
049: * This class is a wapper for a random access file such as that used for
050: * CACHED table storage.
051: *
052: * @author fredt@users
053: * @version 1.8.0
054: * @since 1.7.2
055: */
056: class ScaledRAFile implements ScaledRAInterface {
057:
058: static final int DATA_FILE_RAF = 0;
059: static final int DATA_FILE_NIO = 1;
060: static final int DATA_FILE_JAR = 2;
061: static final long MAX_NIO_LENGTH = (1L << 28);
062:
063: //
064: final SimpleLog appLog;
065: final RandomAccessFile file;
066: private final boolean readOnly;
067: final String fileName;
068: boolean isNio;
069: boolean bufferDirty = true;
070: final byte[] buffer;
071: final HsqlByteArrayInputStream ba;
072: long bufferOffset;
073:
074: //
075: long seekPosition;
076: long realPosition;
077: static int cacheHit;
078:
079: /**
080: * seekPosition is the position in seek() calls or after reading or writing
081: * realPosition is the file position
082: */
083: static Storage newScaledRAFile(Database database, String name,
084: boolean readonly, int type, String classname, String key)
085: throws FileNotFoundException, IOException {
086:
087: if (classname != null) {
088: try {
089: Class zclass = Class.forName(classname);
090: Constructor constructor = zclass
091: .getConstructor(new Class[] { String.class,
092: Boolean.class, Object.class });
093:
094: return (Storage) constructor.newInstance(new Object[] {
095: name, new Boolean(readonly), key });
096: } catch (ClassNotFoundException e) {
097: throw new IOException();
098: } catch (NoSuchMethodException e) {
099: throw new IOException();
100: } catch (InstantiationException e) {
101: throw new IOException();
102: } catch (IllegalAccessException e) {
103: throw new IOException();
104: } catch (java.lang.reflect.InvocationTargetException e) {
105: throw new IOException();
106: }
107: }
108:
109: if (type == DATA_FILE_JAR) {
110: return new ScaledRAFileInJar(name);
111: } else if (type == DATA_FILE_RAF) {
112: return new ScaledRAFile(database, name, readonly);
113: } else {
114: RandomAccessFile file = new RandomAccessFile(name,
115: readonly ? "r" : "rw");
116:
117: if (file.length() > MAX_NIO_LENGTH) {
118: return new ScaledRAFile(database, name, file, readonly);
119: } else {
120: file.close();
121: }
122:
123: try {
124: Class.forName("java.nio.MappedByteBuffer");
125:
126: Class c = Class
127: .forName("org.hsqldb.persist.ScaledRAFileHybrid");
128: Constructor constructor = c.getConstructor(new Class[] {
129: Database.class, String.class, boolean.class });
130:
131: return (ScaledRAInterface) constructor
132: .newInstance(new Object[] { database, name,
133: new Boolean(readonly) });
134: } catch (Exception e) {
135: return new ScaledRAFile(database, name, readonly);
136: }
137: }
138: }
139:
140: ScaledRAFile(Database database, String name, RandomAccessFile file,
141: boolean readonly) throws FileNotFoundException, IOException {
142:
143: this .appLog = database.logger.appLog;
144: this .readOnly = readonly;
145: this .fileName = name;
146: this .file = file;
147:
148: int bufferScale = database.getProperties().getIntegerProperty(
149: HsqlDatabaseProperties.hsqldb_raf_buffer_scale, 12, 8,
150: 13);
151: int bufferSize = 1 << bufferScale;
152:
153: buffer = new byte[bufferSize];
154: ba = new HsqlByteArrayInputStream(buffer);
155: }
156:
157: ScaledRAFile(Database database, String name, boolean readonly)
158: throws FileNotFoundException, IOException {
159:
160: this .appLog = database.logger.appLog;
161: this .readOnly = readonly;
162: this .fileName = name;
163: this .file = new RandomAccessFile(name, readonly ? "r" : "rw");
164:
165: int bufferScale = database.getProperties().getIntegerProperty(
166: HsqlDatabaseProperties.hsqldb_raf_buffer_scale, 12);
167: int bufferSize = 1 << bufferScale;
168:
169: buffer = new byte[bufferSize];
170: ba = new HsqlByteArrayInputStream(buffer);
171: }
172:
173: public long length() throws IOException {
174: return file.length();
175: }
176:
177: /**
178: * Some JVM's do not allow seek beyond end of file, so zeros are written
179: * first in that case. Reported by bohgammer@users in Open Disucssion
180: * Forum.
181: */
182: public void seek(long position) throws IOException {
183:
184: if (!readOnly && file.length() < position) {
185: long tempSize = position - file.length();
186:
187: if (tempSize > 1 << 18) {
188: tempSize = 1 << 18;
189: }
190:
191: byte[] temp = new byte[(int) tempSize];
192:
193: try {
194: long pos = file.length();
195:
196: for (; pos < position - tempSize; pos += tempSize) {
197: file.seek(pos);
198: file.write(temp, 0, (int) tempSize);
199: }
200:
201: file.seek(pos);
202: file.write(temp, 0, (int) (position - pos));
203:
204: realPosition = position;
205: } catch (IOException e) {
206: appLog.logContext(e, null);
207:
208: throw e;
209: }
210: }
211:
212: seekPosition = position;
213: }
214:
215: public long getFilePointer() throws IOException {
216: return seekPosition;
217: }
218:
219: private void readIntoBuffer() throws IOException {
220:
221: long filePos = seekPosition;
222: long subOffset = filePos % buffer.length;
223: long fileLength = file.length();
224: long readLength = fileLength - (filePos - subOffset);
225:
226: try {
227: if (readLength <= 0) {
228: throw new IOException("read beyond end of file");
229: }
230:
231: if (readLength > buffer.length) {
232: readLength = buffer.length;
233: }
234:
235: file.seek(filePos - subOffset);
236: file.readFully(buffer, 0, (int) readLength);
237:
238: bufferOffset = filePos - subOffset;
239: realPosition = bufferOffset + readLength;
240: bufferDirty = false;
241: } catch (IOException e) {
242: resetPointer();
243: appLog.logContext(e, "" + realPosition + " " + readLength);
244:
245: throw e;
246: }
247: }
248:
249: public int read() throws IOException {
250:
251: try {
252: long fileLength = file.length();
253:
254: if (seekPosition >= fileLength) {
255: return -1;
256: }
257:
258: if (bufferDirty || seekPosition < bufferOffset
259: || seekPosition >= bufferOffset + buffer.length) {
260: readIntoBuffer();
261: } else {
262: cacheHit++;
263: }
264:
265: ba.reset();
266: ba.skip(seekPosition - bufferOffset);
267:
268: int val = ba.read();
269:
270: seekPosition++;
271:
272: return val;
273: } catch (IOException e) {
274: resetPointer();
275: appLog.logContext(e, null);
276:
277: throw e;
278: }
279: }
280:
281: public long readLong() throws IOException {
282:
283: try {
284: if (bufferDirty || seekPosition < bufferOffset
285: || seekPosition >= bufferOffset + buffer.length) {
286: readIntoBuffer();
287: } else {
288: cacheHit++;
289: }
290:
291: ba.reset();
292:
293: if (seekPosition - bufferOffset != ba.skip(seekPosition
294: - bufferOffset)) {
295: throw new EOFException();
296: }
297:
298: long val;
299:
300: try {
301: val = ba.readLong();
302: } catch (EOFException e) {
303: file.seek(seekPosition);
304:
305: val = file.readLong();
306: realPosition = file.getFilePointer();
307: }
308:
309: seekPosition += 8;
310:
311: return val;
312: } catch (IOException e) {
313: resetPointer();
314: appLog.logContext(e, null);
315:
316: throw e;
317: }
318: }
319:
320: public int readInt() throws IOException {
321:
322: try {
323: if (bufferDirty || seekPosition < bufferOffset
324: || seekPosition >= bufferOffset + buffer.length) {
325: readIntoBuffer();
326: } else {
327: cacheHit++;
328: }
329:
330: ba.reset();
331:
332: if (seekPosition - bufferOffset != ba.skip(seekPosition
333: - bufferOffset)) {
334: throw new EOFException();
335: }
336:
337: int val;
338:
339: try {
340: val = ba.readInt();
341: } catch (EOFException e) {
342: file.seek(seekPosition);
343:
344: val = file.readInt();
345: realPosition = file.getFilePointer();
346: }
347:
348: seekPosition += 4;
349:
350: return val;
351: } catch (IOException e) {
352: resetPointer();
353: appLog.logContext(e, null);
354:
355: throw e;
356: }
357: }
358:
359: public void read(byte[] b, int offset, int length)
360: throws IOException {
361:
362: try {
363: if (bufferDirty || seekPosition < bufferOffset
364: || seekPosition >= bufferOffset + buffer.length) {
365: readIntoBuffer();
366: } else {
367: cacheHit++;
368: }
369:
370: ba.reset();
371:
372: if (seekPosition - bufferOffset != ba.skip(seekPosition
373: - bufferOffset)) {
374: throw new EOFException();
375: }
376:
377: int bytesRead = ba.read(b, offset, length);
378:
379: seekPosition += bytesRead;
380:
381: if (bytesRead < length) {
382: if (seekPosition != realPosition) {
383: file.seek(seekPosition);
384: }
385:
386: file.readFully(b, offset + bytesRead, length
387: - bytesRead);
388:
389: seekPosition += (length - bytesRead);
390: realPosition = seekPosition;
391: }
392: } catch (IOException e) {
393: resetPointer();
394: appLog.logContext(e, null);
395:
396: throw e;
397: }
398: }
399:
400: public void write(byte[] b, int off, int len) throws IOException {
401:
402: try {
403: if (realPosition != seekPosition) {
404: file.seek(seekPosition);
405:
406: realPosition = seekPosition;
407: }
408:
409: if (seekPosition >= bufferOffset
410: && seekPosition < bufferOffset + buffer.length) {
411: bufferDirty = true;
412: }
413:
414: file.write(b, off, len);
415:
416: seekPosition += len;
417: realPosition = seekPosition;
418: } catch (IOException e) {
419: resetPointer();
420: appLog.logContext(e, null);
421:
422: throw e;
423: }
424: }
425:
426: public void writeInt(int i) throws IOException {
427:
428: try {
429: if (realPosition != seekPosition) {
430: file.seek(seekPosition);
431:
432: realPosition = seekPosition;
433: }
434:
435: if (seekPosition >= bufferOffset
436: && seekPosition < bufferOffset + buffer.length) {
437: bufferDirty = true;
438: }
439:
440: file.writeInt(i);
441:
442: seekPosition += 4;
443: realPosition = seekPosition;
444: } catch (IOException e) {
445: resetPointer();
446: appLog.logContext(e, null);
447:
448: throw e;
449: }
450: }
451:
452: public void writeLong(long i) throws IOException {
453:
454: try {
455: if (realPosition != seekPosition) {
456: file.seek(seekPosition);
457:
458: realPosition = seekPosition;
459: }
460:
461: if (seekPosition >= bufferOffset
462: && seekPosition < bufferOffset + buffer.length) {
463: bufferDirty = true;
464: }
465:
466: file.writeLong(i);
467:
468: seekPosition += 8;
469: realPosition = seekPosition;
470: } catch (IOException e) {
471: resetPointer();
472: appLog.logContext(e, null);
473:
474: throw e;
475: }
476: }
477:
478: public void close() throws IOException {
479: Trace.printSystemOut("cache hit " + cacheHit);
480: file.close();
481: }
482:
483: public boolean isReadOnly() {
484: return readOnly;
485: }
486:
487: public boolean wasNio() {
488: return false;
489: }
490:
491: public boolean canAccess(int length) {
492: return true;
493: }
494:
495: public boolean canSeek(long position) {
496: return true;
497: }
498:
499: public Database getDatabase() {
500: return null;
501: }
502:
503: private void resetPointer() {
504:
505: try {
506: bufferDirty = true;
507:
508: file.seek(seekPosition);
509:
510: realPosition = seekPosition;
511: } catch (Throwable e) {
512: }
513: }
514: }
|