/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package scouter.io;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import scouter.util.FileUtil;
import scouter.util.IClose;
import scouter.util.LongKeyLinkedMap;
public class BufferedRandomAccessX implements IClose {
final private RandomAccessFile file;
final private LongKeyLinkedMap<byte[]> buffer;
final private int blockSize;
private long fileLength = 0;
public long readAccessCount;
private FileChannel channel;
public BufferedRandomAccessX(RandomAccessFile file) throws IOException {
this(file, 8192, 1, false);
}
public BufferedRandomAccessX(RandomAccessFile file, boolean useNio) throws IOException {
this(file, 8192, 1, useNio);
}
public BufferedRandomAccessX(RandomAccessFile file, int blockSize, int numberOfBlock) throws IOException {
this(file, blockSize, numberOfBlock, false);
}
public BufferedRandomAccessX(RandomAccessFile file, int blockSize, int numberOfBlock, boolean useNio)
throws IOException {
this.file = file;
this.buffer = new LongKeyLinkedMap<byte[]>().setMax(numberOfBlock);
this.blockSize = blockSize;
this.fileLength = file.length();
if (useNio) {
this.channel = this.file.getChannel();
}
}
private long offset=0;;
public synchronized byte[] read(long pos, int length) throws IOException {
if (pos >= this.fileLength)
return null;
this.offset = pos + length;
DataOutputX out = new DataOutputX();
int posOnBlock = (int) (pos % blockSize);
if (posOnBlock > 0) {
//long blockNum = (pos / blockSize);
byte[] blockBytes = getReadBlock(pos / blockSize);
if(blockBytes==null){
return null;
}
if (blockBytes.length < blockSize) {
int readLen = Math.min(blockBytes.length - posOnBlock, length);
return DataInputX.get(blockBytes, posOnBlock, readLen);
}
int readLen = Math.min(blockSize - posOnBlock, length);
out.write(DataInputX.get(blockBytes, posOnBlock, readLen));
length -= readLen;
pos += readLen;
}
int blockCount = length / blockSize;
for (int i = 0; i < blockCount; i++) {
//long blockNum = (int) (pos / blockSize);
byte[] blockBytes = getReadBlock(pos / blockSize);
if (blockBytes == null) {
return out.toByteArray();
}
out.write(blockBytes);
pos += blockSize;
length -= blockSize;
}
if (length == 0) {
return out.toByteArray();
}
int remainder = length;
// long blockNum = (int) (pos / blockSize);
byte[] block = getReadBlock(pos / blockSize);
if (block != null) {
remainder = Math.min(block.length, remainder);
out.write(DataInputX.get(block, 0, remainder));
}
return out.toByteArray();
}
public long getLength() throws IOException {
if (this.channel == null) {
return this.file.length();
} else {
return this.channel.size();
}
}
public void append(byte[] data) throws IOException {
this.write(this.fileLength, data);
}
public synchronized void write(long pos, byte[] data) throws IOException {
if (data == null || data.length == 0)
return;
if (pos >= this.fileLength) {
this.offset = this.fileLength + data.length;
this.seek(this.fileLength);
writeReal(data);
return;
}
this.offset = pos + data.length;
seek(pos);
writeReal(data);
// /////////////////
int length = data.length;
int offset = 0;
int posOnBlock = (int) (pos % blockSize);
if (posOnBlock > 0) {
long blockNumber = (pos / blockSize);
byte[] blockBytes = this.buffer.get(blockNumber);
int writeLen = Math.min(blockSize - posOnBlock, length);
if (blockBytes != null) {
if (blockSize == blockBytes.length) {
System.arraycopy(data, offset, blockBytes, posOnBlock, writeLen);
} else {
this.buffer.remove(blockNumber);
}
}
length -= writeLen;
pos += writeLen;
offset += writeLen;
}
if (length == 0)
return;
int blockCount = length / blockSize;
for (int i = 0; i < blockCount; i++) {
long blockNumber = pos / blockSize;
byte[] blockBytes = this.buffer.get(blockNumber);
if (blockBytes != null) {
if (blockSize == blockBytes.length) {
System.arraycopy(data, offset, blockBytes, 0, blockSize);
} else {
this.buffer.remove(blockNumber);
}
}
length -= blockSize;
pos += blockSize;
offset += blockSize;
}
int remainder = length;
if (remainder == 0)
return;
if (remainder < 0) {
throw new IOException("Write fail remainder=" + remainder + " pos=" + pos);
}
long blockNumber = pos / blockSize;
byte[] blockBytes = this.buffer.get(blockNumber);
if (blockBytes == null)
return;
if (blockSize == blockBytes.length) {
System.arraycopy(data, offset, blockBytes, 0, remainder);
} else {
this.buffer.remove(blockNumber);
}
}
private void writeReal(byte[] data) throws IOException {
if (this.channel == null) {
this.file.write(data);
} else {
ByteBuffer dst = ByteBuffer.wrap(data);
channel.write(dst);
}
this.fileLength = this.getLength();
}
private byte[] getReadBlock(long blockNumber) throws IOException {
byte[] blockBytes = this.buffer.get(blockNumber);
if (blockBytes != null) {
if (blockBytes.length == blockSize)
return blockBytes;
}
long pos = blockNumber * blockSize;
if(pos >=this.fileLength)
return null;
this.readAccessCount++;
this.seek(pos);
long len = Math.min((this.fileLength - pos), blockSize);
blockBytes = new byte[(int) len];
readReal(blockBytes);
this.buffer.put(blockNumber, blockBytes);
return blockBytes;
}
private void seek(long pos) throws IOException {
if (this.channel == null) {
this.file.seek(pos);
} else {
this.channel.position(pos);
}
}
private void readReal(byte[] block) throws IOException {
if (this.channel == null) {
this.file.readFully(block);
} else {
ByteBuffer dst = ByteBuffer.wrap(block);
channel.read(dst);
}
}
public void close() {
if (this.channel != null) {
FileUtil.close(this.channel);
}
FileUtil.close(this.file);
}
public int readInt(long pos) throws IOException {
byte[] buf = read(pos, 4);
return DataInputX.toInt(buf, 0);
}
public void writeInt(long pos, int i) throws IOException {
this.write(pos, DataOutputX.toBytes(i));
}
public byte readByte(long pos) throws IOException {
byte[] buf = read(pos, 1);
return buf[0];
}
public long getOffset(){
return this.offset;
}
public short readShort(long pos) throws IOException {
byte[] buf = read(pos, 2);
return DataInputX.toShort(buf, 0);
}
}