/*
* Copyright (C) 2014 James Lawrence.
*
* This file is part of LibLab.
*
* LibLab is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sqrt.liblab.io;
import com.sqrt.liblab.LabFile;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.BufferOverflowException;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
/**
* This DataSource maps the area of the file we're interested in into memory when required and then performs reads
* from there. Closing this datasource unmaps the file from memory if mapped.
*/
public class DiskDataSource extends DataSource {
private static final int BUFFER_SIZE = 1 * 1024 * 1024; // Page 1MB of data
private MappedByteBuffer map;
private final RandomAccessFile source;
private long _pos;
/**
* The offset into the source file that the data we're interested in starts at
*/
public final long start;
/**
* The length of the data we're interested in
*/
public final long len;
/**
* Constructs a DiskDataSource that gets its data from the specified RandomAccessFile
* @param container the container that contains this data
* @param name the name of the entry we have the data for
* @param source the source of the data
* @param start the offset into the source that the data we're interested in starts at
* @param len the length of the data we're interested in
*/
public DiskDataSource(LabFile container, String name, RandomAccessFile source, long start, long len) {
super(container, name);
this.start = start;
this.len = len;
this.source = source;
}
/**
* Constructs a DiskDataSource that gets its data from the specified RandomAccessFile
* @param container the container that contains this data
* @param name the name of the entry we have the data for
* @param source the source of the data
* @throws IOException
*/
public DiskDataSource(LabFile container, String name, RandomAccessFile source) throws IOException {
this(container, name, source, 0, source.length());
}
/**
* Returns a DiskDataSource that only reads the specified subsection of the data
* @param off the offset into this datasource that the subsections data will start at
* @param len the length of the data the subsection will read
* @return the new data source
*/
public synchronized DiskDataSource slice(int off, int len) {
return new DiskDataSource(container, getName(), source, start + off, len);
}
private void ensureMapped(long pos, int len) throws IOException {
if (map == null || pos < _pos || pos + len > _pos + map.capacity()) {
_pos = pos;
long nlen = Math.max(Math.min(BUFFER_SIZE, this.len - _pos), len);
if(_pos + nlen > this.len)
throw new BufferOverflowException(); // Todo: this is required when reading from a LAB, but will restrict
// when writing to a new file... need canExpand or something
map = source.getChannel().map(FileChannel.MapMode.READ_ONLY, start + _pos, nlen);
map.position(0);
}
}
private void ensureMapped(int len) throws IOException {
ensureMapped(position(), len);
}
public synchronized long position() throws IOException {
return map == null? 0: (_pos + map.position());
}
public synchronized void position(long pos) throws IOException {
ensureMapped(pos, 0);
map.position((int) (pos - _pos));
}
public long length() {
return len;
}
public synchronized void get(byte[] b, int off, int len) throws IOException {
ensureMapped(len);
map.get(b, off, len);
}
public synchronized void put(byte[] b, int off, int len) throws IOException {
ensureMapped(len);
map.put(b, off, len);
}
public byte get() throws IOException {
ensureMapped(1);
return map.get();
}
public void put(byte b) throws IOException {
map.put(b);
}
public synchronized int getUByte() throws IOException {
ensureMapped(1);
return map.get() & 0xff;
}
/**
* Unmaps the data from memory (if previously mapped)
* @throws IOException
*/
public synchronized void close() throws IOException {
if (map == null)
return;
map.clear();
map = null;
}
public boolean markSupported() {
return true;
}
public short getShort() throws IOException {
ensureMapped(2);
be();
return map.getShort();
}
public void putShort(short s) throws IOException {
ensureMapped(2);
be();
map.putShort(s);
}
public short getShortLE() throws IOException {
ensureMapped(2);
le();
return map.getShort();
}
public void putShortLE(short s) throws IOException {
ensureMapped(2);
le();
map.putShort(s);
}
public int getInt() throws IOException {
ensureMapped(4);
be();
return map.getInt();
}
public void putInt(int i) throws IOException {
ensureMapped(4);
be();
map.putInt(i);
}
public int getIntLE() throws IOException {
ensureMapped(4);
le();
return map.getInt();
}
public void putIntLE(int i) throws IOException {
ensureMapped(4);
le();
map.putInt(i);
}
private void be() {
map.order(ByteOrder.BIG_ENDIAN);
}
private void le() {
map.order(ByteOrder.LITTLE_ENDIAN);
}
@Override
public int hashCode() {
return (int) (source.hashCode() + start + len);
}
public static DiskDataSource createTempDataSource(LabFile container, String name) throws IOException {
File f = File.createTempFile(name, "tmp");
RandomAccessFile raf = new RandomAccessFile(f, "rw");
return new DiskDataSource(container, name, raf);
}
}