/* Copyright (c) 2001-2016, 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.
FileDescriptor;
import java.io.
IOException;
import java.io.
RandomAccessFile;
import java.lang.reflect.
InvocationTargetException;
import java.lang.reflect.
Method;
import java.nio.
MappedByteBuffer;
import java.nio.channels.
FileChannel;
import java.nio.channels.
FileChannel.
MapMode;
import org.hsqldb.
Database;
import org.hsqldb.error.
Error;
import org.hsqldb.error.
ErrorCode;
import org.hsqldb.lib.
ArrayUtil;
import org.hsqldb.lib.java.
JavaSystem;
/**
* NIO version of ScaledRAFile. This class is used only for storing a CACHED
* TABLE .data file and cannot be used for TEXT TABLE source files.
*
* Once the maximum data file size allowed for NIO is reached, an ordinary
* ScaledRAFile is used for data access.
*
* @author Fred Toussi (fredt@users dot sourceforge.net)
* @version 2.3.3
* @since 1.8.0.5
*/
final class
RAFileNIO implements
RandomAccessInterface {
private final
EventLogInterface logger;
private final boolean
readOnly;
private final long
maxLength;
private long
fileLength;
private
RandomAccessFile file;
private
FileDescriptor fileDescriptor;
private
MappedByteBuffer buffer;
private long
bufferPosition;
private int
bufferLength;
private long
currentPosition;
private
FileChannel channel;
private boolean
buffersModified;
//
private
MappedByteBuffer[]
buffers = new
MappedByteBuffer[]{};
//
private static final
String JVM_ERROR = "NIO access failed";
//
static final int
largeBufferScale = 24;
static final int
largeBufferSize = 1 <<
largeBufferScale;
static final long
largeBufferMask = 0xffffffffffffffffL
<<
largeBufferScale;
RAFileNIO(
EventLogInterface logger,
String name, boolean
readOnly,
long
requiredLength, long
maxLength) throws
IOException {
this.
logger =
logger;
this.
maxLength =
maxLength;
java.io.
File tempFile = new java.io.
File(
name);
if (
readOnly) {
requiredLength =
tempFile.
length();
} else {
if (
tempFile.
length() >
requiredLength) {
requiredLength =
tempFile.
length();
}
requiredLength =
ArrayUtil.
getBinaryNormalisedCeiling(
requiredLength,
largeBufferScale);
}
file = new
RandomAccessFile(
name,
readOnly ? "r"
: "rw");
this.
readOnly =
readOnly;
this.
channel =
file.
getChannel();
this.
fileDescriptor =
file.
getFD();
if (
ensureLength(
requiredLength)) {
buffer =
buffers[0];
bufferLength =
buffer.
limit();
bufferPosition = 0;
currentPosition = 0;
} else {
close();
IOException io = new
IOException("NIO buffer allocation failed");
throw
io;
}
}
public long
length() throws
IOException {
try {
return
file.
length();
} catch (
IOException e) {
logger.
logWarningEvent(
JVM_ERROR,
e);
throw
e;
} catch (
Throwable t) {
logger.
logWarningEvent(
JVM_ERROR,
t);
IOException io =
JavaSystem.
toIOException(
t);
throw
io;
}
}
public void
seek(long
newPos) throws
IOException {
try {
positionBufferSeek(
newPos);
buffer.
position((int) (
newPos -
bufferPosition));
} catch (
IllegalArgumentException e) {
logger.
logWarningEvent(
JVM_ERROR,
e);
IOException io =
JavaSystem.
toIOException(
e);
throw
io;
} catch (
Throwable t) {
logger.
logWarningEvent(
JVM_ERROR,
t);
IOException io =
JavaSystem.
toIOException(
t);
throw
io;
}
}
public long
getFilePointer() throws
IOException {
try {
return
currentPosition;
} catch (
Throwable t) {
logger.
logWarningEvent(
JVM_ERROR,
t);
IOException io =
JavaSystem.
toIOException(
t);
throw
io;
}
}
public int
read() throws
IOException {
try {
int
value =
buffer.
get();
positionBufferMove(1);
return
value;
} catch (
Throwable t) {
logger.
logWarningEvent(
JVM_ERROR,
t);
IOException io =
JavaSystem.
toIOException(
t);
throw
io;
}
}
public void
read(byte[]
b, int
offset, int
length) throws
IOException {
try {
while (true) {
checkBuffer();
long
transferLength =
bufferPosition +
bufferLength
-
currentPosition;
if (
transferLength >
length) {
transferLength =
length;
}
buffer.
get(
b,
offset, (int)
transferLength);
positionBufferMove((int)
transferLength);
length -=
transferLength;
offset +=
transferLength;
if (
length == 0) {
break;
}
}
} catch (
Throwable t) {
logger.
logWarningEvent(
JVM_ERROR,
t);
IOException io =
JavaSystem.
toIOException(
t);
throw
io;
}
}
public int
readInt() throws
IOException {
try {
int
value =
buffer.
getInt();
positionBufferMove(4);
return
value;
} catch (
Throwable t) {
logger.
logWarningEvent(
JVM_ERROR,
t);
IOException io =
JavaSystem.
toIOException(
t);
throw
io;
}
}
public long
readLong() throws
IOException {
try {
long
value =
buffer.
getLong();
positionBufferMove(8);
return
value;
} catch (
Throwable t) {
logger.
logWarningEvent(
JVM_ERROR,
t);
IOException io =
JavaSystem.
toIOException(
t);
throw
io;
}
}
public void
write(byte[]
b, int
offset, int
length) throws
IOException {
long
transferLength;
try {
buffersModified = true;
while (true) {
checkBuffer();
transferLength =
bufferPosition +
bufferLength
-
currentPosition;
if (
transferLength >
length) {
transferLength =
length;
}
buffer.
put(
b,
offset, (int)
transferLength);
positionBufferMove((int)
transferLength);
length -=
transferLength;
offset +=
transferLength;
if (
length == 0) {
break;
}
}
} catch (
Throwable t) {
logger.
logWarningEvent(
JVM_ERROR,
t);
IOException io =
JavaSystem.
toIOException(
t);
throw
io;
}
}
public void
writeInt(int
i) throws
IOException {
try {
buffersModified = true;
buffer.
putInt(
i);
positionBufferMove(4);
} catch (
Throwable t) {
logger.
logWarningEvent(
JVM_ERROR,
t);
IOException io =
JavaSystem.
toIOException(
t);
throw
io;
}
}
public void
writeLong(long
i) throws
IOException {
try {
buffersModified = true;
buffer.
putLong(
i);
positionBufferMove(8);
} catch (
Throwable t) {
logger.
logWarningEvent(
JVM_ERROR,
t);
IOException io =
JavaSystem.
toIOException(
t);
throw
io;
}
}
public void
close() throws
IOException {
try {
logger.
logDetailEvent("NIO file close, size: " +
fileLength);
buffer = null;
channel = null;
for (int
i = 0;
i <
buffers.length;
i++) {
unmap(
buffers[
i]);
buffers[
i] = null;
}
file.
close();
// System.gc();
} catch (
Throwable t) {
logger.
logWarningEvent("NIO buffer close error",
t);
IOException io =
JavaSystem.
toIOException(
t);
throw
io;
}
}
public boolean
isReadOnly() {
return
readOnly;
}
public boolean
ensureLength(long
newLength) {
if (
newLength >
maxLength) {
return false;
}
while (
newLength >
fileLength) {
if (!
enlargeFile(
newLength)) {
return false;
}
}
return true;
}
private boolean
enlargeFile(long
newFileLength) {
try {
long
newBufferLength =
newFileLength;
if (!
readOnly) {
newBufferLength =
largeBufferSize;
}
MapMode mapMode =
readOnly ?
FileChannel.
MapMode.
READ_ONLY
:
FileChannel.
MapMode.
READ_WRITE;
if (!
readOnly &&
file.
length() <
fileLength +
newBufferLength) {
file.
seek(
fileLength +
newBufferLength - 1);
file.
writeByte(0);
}
MappedByteBuffer[]
newBuffers =
new
MappedByteBuffer[
buffers.length + 1];
MappedByteBuffer newBuffer =
channel.
map(
mapMode,
fileLength,
newBufferLength);
System.
arraycopy(
buffers, 0,
newBuffers, 0,
buffers.length);
newBuffers[
buffers.length] =
newBuffer;
buffers =
newBuffers;
fileLength +=
newBufferLength;
logger.
logDetailEvent("NIO buffer instance, file size "
+
fileLength);
} catch (
Throwable e) {
logger.
logDetailEvent("NOI buffer allocate failed, file size "
+
newFileLength);
return false;
}
return true;
}
public boolean
setLength(long
newLength) {
if (
newLength >
fileLength) {
return
enlargeFile(
newLength);
} else {
try {
seek(0);
} catch (
Throwable t) {
//
}
return true;
}
}
public
Database getDatabase() {
return null;
}
public void
synch() {
boolean
error = false;
int
errIndex = 0;
for (int
i = 0;
i <
buffers.length;
i++) {
try {
buffers[
i].
force();
} catch (
Throwable t) {
logger.
logWarningEvent("NIO buffer force error: pos "
+
i *
largeBufferSize + " ",
t);
if (!
error) {
errIndex =
i;
}
error = true;
}
}
if (
error) {
for (int
i =
errIndex;
i <
buffers.length;
i++) {
try {
buffers[
i].
force();
} catch (
Throwable t) {
logger.
logWarningEvent("NIO buffer force error "
+
i *
largeBufferSize + " ",
t);
}
}
}
try {
fileDescriptor.
sync();
buffersModified = false;
} catch (
Throwable t) {
logger.
logSevereEvent("NIO RA file sync error ",
t);
throw
Error.
error(
t,
ErrorCode.
FILE_IO_ERROR, null);
}
}
private void
positionBufferSeek(long
offset) {
if (
offset <
bufferPosition
||
offset >=
bufferPosition +
bufferLength) {
setCurrentBuffer(
offset);
}
buffer.
position((int) (
offset -
bufferPosition));
currentPosition =
offset;
}
private void
positionBufferMove(int
relOffset) {
long
offset =
currentPosition +
relOffset;
if (
offset >=
bufferPosition +
bufferLength) {
setCurrentBuffer(
offset);
}
buffer.
position((int) (
offset -
bufferPosition));
currentPosition =
offset;
}
private void
setCurrentBuffer(long
offset) {
if(
readOnly) {
return;
}
int
bufferIndex = (int) (
offset >>
largeBufferScale);
// when moving to last position in file
if (
bufferIndex ==
buffers.length) {
bufferIndex =
buffers.length - 1;
bufferPosition = (long)
bufferIndex *
largeBufferSize;
buffer =
buffers[
bufferIndex];
return;
}
buffer =
buffers[
bufferIndex];
bufferPosition =
offset &
largeBufferMask;
}
/**
* checks for two types of potential errors before reads and writes and
* fixes them.
*
*/
private void
checkBuffer() {
if(
readOnly) {
return;
}
int
bufferIndex = (int) (
currentPosition >>
largeBufferScale);
if (
currentPosition !=
bufferPosition +
buffer.
position()) {
buffer =
buffers[
bufferIndex];
bufferPosition =
currentPosition &
largeBufferMask;
buffer.
position((int) (
currentPosition -
bufferPosition));
} else if (
buffer !=
buffers[
bufferIndex]) {
buffer =
buffers[
bufferIndex];
}
}
/**
* Non-essential unmap method - see http://bugs.sun.com/view_bug.do?bug_id=4724038
* reported by joel_turkel at users.sourceforge.net
*/
private void
unmap(
MappedByteBuffer buffer) throws
IOException {
if (
buffer == null) {
return;
}
try {
Method cleanerMethod =
buffer.
getClass().
getMethod("cleaner");
cleanerMethod.
setAccessible(true);
Object cleaner =
cleanerMethod.
invoke(
buffer);
Method clearMethod =
cleaner.
getClass().
getMethod("clean");
clearMethod.
invoke(
cleaner);
} catch (
InvocationTargetException e) {}
catch (
NoSuchMethodException e) {
// Means we're not dealing with a Sun JVM?
} catch (
Throwable e) {}
}
}