/* Copyright (c) 2001-2018, The HSQL Development Group
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the HSQL Development Group nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.hsqldb.persist;
import java.io.
EOFException;
import java.io.
FileDescriptor;
import java.io.
FileNotFoundException;
import java.io.
IOException;
import java.io.
RandomAccessFile;
import java.lang.reflect.
Constructor;
import org.hsqldb.
Database;
import org.hsqldb.error.
Error;
import org.hsqldb.error.
ErrorCode;
import org.hsqldb.lib.
ArrayUtil;
import org.hsqldb.lib.
HsqlByteArrayInputStream;
import org.hsqldb.lib.
HsqlByteArrayOutputStream;
import org.hsqldb.lib.
Storage;
/**
* This class is a wrapper for a random access file such as that used for
* CACHED table storage.
*
* @author Fred Toussi (fredt@users dot sourceforge.net)
* @version 2.3.3
* @since 1.7.2
*/
final class
RAFile implements
RandomAccessInterface {
static final int
DATA_FILE_RAF = 0;
static final int
DATA_FILE_NIO = 1;
static final int
DATA_FILE_JAR = 2;
static final int
DATA_FILE_STORED = 3;
static final int
DATA_FILE_SINGLE = 4;
static final int
DATA_FILE_TEXT = 5;
//
static final int
bufferScale = 12;
static final int
bufferSize = 1 <<
bufferScale;
static final long
bufferMask = 0xffffffffffffffffL <<
bufferScale;
//
final
EventLogInterface logger;
final
RandomAccessFile file;
final
FileDescriptor fileDescriptor;
private final boolean
readOnly;
final
String fileName;
final byte[]
buffer;
final
HsqlByteArrayInputStream ba;
final byte[]
valueBuffer;
final
HsqlByteArrayOutputStream vbao;
final
HsqlByteArrayInputStream vbai;
long
bufferOffset;
long
fileLength;
final boolean
extendLength;
//
long
seekPosition;
int
cacheHit;
/**
* seekPosition is the position in seek() calls or after reading or writing
* realPosition is the file position
*/
static
RandomAccessInterface newScaledRAFile(
Database database,
String name, boolean
readonly,
int
type) throws
FileNotFoundException,
IOException {
if (
type ==
DATA_FILE_STORED) {
try {
String cname =
database.
getURLProperties().
getProperty(
HsqlDatabaseProperties.
url_storage_class_name);
String skey =
database.
getURLProperties().
getProperty(
HsqlDatabaseProperties.
url_storage_key);
Class storageClass;
try {
ClassLoader classLoader =
Thread.
currentThread().
getContextClassLoader();
storageClass =
classLoader.
loadClass(
cname);
} catch (
ClassNotFoundException e) {
storageClass =
Class.
forName(
cname);
}
Constructor constructor =
storageClass.
getConstructor(new
Class[] {
String.class,
Boolean.class,
Object.class
});
Object accessor =
constructor.
newInstance(new
Object[] {
name,
Boolean.
valueOf(
readonly),
skey
});
if (
accessor instanceof
RandomAccessInterface) {
return (
RandomAccessInterface)
accessor;
} else if (
accessor instanceof org.hsqldb.lib.
Storage) {
return new
RAStorageWrapper((
Storage)
accessor);
} else {
throw new
IOException();
}
} catch (
ClassNotFoundException e) {
throw new
IOException();
} catch (
NoSuchMethodException e) {
throw new
IOException();
} catch (
InstantiationException e) {
throw new
IOException();
} catch (
IllegalAccessException e) {
throw new
IOException();
} catch (java.lang.reflect.
InvocationTargetException e) {
throw new
IOException();
}
}
if (
type ==
DATA_FILE_JAR) {
return new
RAFileInJar(
name);
} else if (
type ==
DATA_FILE_TEXT) {
return new
RAFile(
database.
logger,
name,
readonly, false,
true);
} else if (
type ==
DATA_FILE_RAF) {
return new
RAFile(
database.
logger,
name,
readonly, true, false);
} else {
java.io.
File fi = new java.io.
File(
name);
long
length =
fi.
length();
if (
length >
database.
logger.
propNioMaxSize) {
return new
RAFile(
database.
logger,
name,
readonly, true,
false);
}
return new
RAFileHybrid(
database,
name,
readonly);
}
}
RAFile(
EventLogInterface logger,
String name, boolean
readonly,
boolean
extendLengthToBlock,
boolean
commitOnChange) throws
FileNotFoundException,
IOException {
this.
logger =
logger;
this.
fileName =
name;
this.
readOnly =
readonly;
this.
extendLength =
extendLengthToBlock;
String accessMode =
readonly ? "r"
:
commitOnChange ? "rws"
: "rw";
this.
file = new
RandomAccessFile(
name,
accessMode);
buffer = new byte[
bufferSize];
ba = new
HsqlByteArrayInputStream(
buffer);
valueBuffer = new byte[8];
vbao = new
HsqlByteArrayOutputStream(
valueBuffer);
vbai = new
HsqlByteArrayInputStream(
valueBuffer);
fileDescriptor =
file.
getFD();
fileLength =
length();
readIntoBuffer();
}
public long
length() throws
IOException {
return
file.
length();
}
public void
seek(long
position) throws
IOException {
if (
readOnly &&
fileLength <
position) {
throw new
IOException("read beyond end of file");
}
seekPosition =
position;
}
public long
getFilePointer() throws
IOException {
return
seekPosition;
}
private void
readIntoBuffer() throws
IOException {
long
filePos =
seekPosition &
bufferMask;
long
readLength =
fileLength -
filePos;
if (
readLength >
buffer.length) {
readLength =
buffer.length;
}
if (
readLength < 0) {
throw new
IOException("read beyond end of file");
}
try {
file.
seek(
filePos);
file.
readFully(
buffer, 0, (int)
readLength);
bufferOffset =
filePos;
} catch (
IOException e) {
resetPointer();
logger.
logWarningEvent("Read Error " +
filePos + " " +
readLength,
e);
throw
e;
}
}
public int
read() throws
IOException {
try {
if (
seekPosition >=
fileLength) {
return -1;
}
if (
seekPosition <
bufferOffset
||
seekPosition >=
bufferOffset +
buffer.length) {
readIntoBuffer();
} else {
cacheHit++;
}
int
val =
buffer[(int) (
seekPosition -
bufferOffset)] & 0xff;
seekPosition++;
return
val;
} catch (
IOException e) {
resetPointer();
logger.
logWarningEvent("read failed",
e);
throw
e;
}
}
public long
readLong() throws
IOException {
vbai.
reset();
read(
valueBuffer, 0, 8);
return
vbai.
readLong();
}
public int
readInt() throws
IOException {
vbai.
reset();
read(
valueBuffer, 0, 4);
return
vbai.
readInt();
}
public void
read(byte[]
b, int
offset, int
length) throws
IOException {
try {
if (
seekPosition +
length >
fileLength) {
throw new
EOFException();
}
if (
length >
buffer.length
&& (
seekPosition <
bufferOffset
||
seekPosition >=
bufferOffset +
buffer.length)) {
file.
seek(
seekPosition);
file.
readFully(
b,
offset,
length);
seekPosition +=
length;
return;
}
if (
seekPosition <
bufferOffset
||
seekPosition >=
bufferOffset +
buffer.length) {
readIntoBuffer();
} else {
cacheHit++;
}
ba.
reset();
if (
seekPosition -
bufferOffset
!=
ba.
skip(
seekPosition -
bufferOffset)) {
throw new
EOFException();
}
int
bytesRead =
ba.
read(
b,
offset,
length);
seekPosition +=
bytesRead;
if (
bytesRead <
length) {
file.
seek(
seekPosition);
file.
readFully(
b,
offset +
bytesRead,
length -
bytesRead);
seekPosition += (
length -
bytesRead);
}
} catch (
IOException e) {
resetPointer();
logger.
logWarningEvent("failed to read a byte array",
e);
throw
e;
}
}
public void
write(byte[]
b, int
off, int
length) throws
IOException {
try {
file.
seek(
seekPosition);
if (
seekPosition <
bufferOffset +
buffer.length
&&
seekPosition +
length >
bufferOffset) {
writeToBuffer(
b,
off,
length);
}
file.
write(
b,
off,
length);
seekPosition +=
length;
if (!
extendLength &&
fileLength <
seekPosition) {
fileLength =
seekPosition;
}
} catch (
IOException e) {
resetPointer();
logger.
logWarningEvent("failed to write a byte array",
e);
throw
e;
}
}
public void
writeInt(int
i) throws
IOException {
vbao.
reset();
vbao.
writeInt(
i);
write(
valueBuffer, 0, 4);
}
public void
writeLong(long
i) throws
IOException {
vbao.
reset();
vbao.
writeLong(
i);
write(
valueBuffer, 0, 8);
}
public void
close() throws
IOException {
file.
close();
}
public boolean
isReadOnly() {
return
readOnly;
}
public boolean
ensureLength(long
newLength) {
if (
newLength <=
fileLength) {
return true;
}
try {
extendLength(
newLength);
} catch (
IOException e) {
return false;
}
return true;
}
public boolean
setLength(long
newLength) {
try {
file.
setLength(
newLength);
file.
seek(0);
fileLength =
file.
length();
seekPosition = 0;
readIntoBuffer();
return true;
} catch (
Throwable t) {
return false;
}
}
public void
synch() {
try {
fileDescriptor.
sync();
} catch (
Throwable t) {
try {
fileDescriptor.
sync();
} catch (
Throwable tt) {
logger.
logSevereEvent("RA file sync error ",
tt);
throw
Error.
error(
t,
ErrorCode.
FILE_IO_ERROR, null);
}
}
}
private int
writeToBuffer(byte[]
b, int
off, int
len) throws
IOException {
int
count =
ArrayUtil.
copyBytes(
seekPosition -
off,
b,
off,
len,
bufferOffset,
buffer,
buffer.length);
return
count;
}
private long
getExtendLength(long
position) {
if (!
extendLength) {
return
position;
}
int
scaleUp;
if (
position < 256 * 1024) {
scaleUp = 2;
} else if (
position < 1024 * 1024) {
scaleUp = 6;
} else if (
position < 32 * 1024 * 1024) {
scaleUp = 8;
} else {
scaleUp = 12;
}
position =
ArrayUtil.
getBinaryNormalisedCeiling(
position,
bufferScale +
scaleUp);
return
position;
}
/**
* Some old JVM's do not allow seek beyond end of file, so zeros must be written
* first in that case. Reported by bohgammer@users in Open Disucssion
* Forum.
*/
private void
extendLength(long
position) throws
IOException {
long
newSize =
getExtendLength(
position);
if (
newSize >
fileLength) {
try {
file.
seek(
newSize - 1);
file.
write(0);
fileLength =
newSize;
} catch (
IOException e) {
logger.
logWarningEvent("data file enlarge failed ",
e);
throw
e;
}
}
}
private void
resetPointer() {
try {
seekPosition = 0;
fileLength =
length();
readIntoBuffer();
} catch (
Throwable e) {}
}
}