/*******************************************************************************
* Copyright (c) 2010 Association for Decentralized Information Management in
* Industry THTH ry.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* VTT Technical Research Centre of Finland - initial API and implementation
*******************************************************************************/
package org.simantics.databoard.file;
import gnu.trove.list.array.TLongArrayList;
import gnu.trove.map.hash.TObjectIntHashMap;
import java.io.File;
import java.io.IOException;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.RandomAccess;
import org.simantics.databoard.binding.Binding;
import org.simantics.databoard.binding.error.RuntimeBindingException;
import org.simantics.databoard.serialization.SerializationException;
import org.simantics.databoard.serialization.Serializer;
import org.simantics.databoard.serialization.SerializerConstructionException;
import org.simantics.databoard.serialization.SerializerScheme;
import org.simantics.databoard.util.binary.RandomAccessBinary;
import org.simantics.databoard.util.binary.RandomAccessBinary.ByteSide;
/**
* BlobList is a {@link RandomAccessBinary} backend implementation of a List
* collection. add() and get() operations serialize and deserialize objects
* from binary format.
*
* Set, remove, insert and add operations flush() modifications to before return.
*
* Each operation may throw {@link RuntimeIOException}, if there is IOException
* in the {@link RandomAccessBinary}
*
* Entry position index is on open if the file has variable width
* data type (eg. String). The entire file is scanned through.
*
* TODO lazy index. Append alone (add()) doesn't require scan.
*
* @see FileList File based implementation
* @author Toni Kalajainen
*/
public class RandomAccessBinaryList extends AbstractList implements IFileList, RandomAccess {
/** Reader */
RandomAccessBinary blob;
/** Format */
SerializerScheme format;
/** Binding */
Binding binding;
/** Serializer */
Serializer serializer;
/** Offset table */
Index table;
/** identities */
List identities = new ArrayList();
/** identities */
TObjectIntHashMap identities2 = new TObjectIntHashMap();
/** Status */
boolean closed = false;
/**
* Create new random access list backed by a file
*
* @param blob blob
* @param binding
* @param startPos The position of the first sample in file
* @param format serialization format
* @throws IOException
* @throws SerializerConstructionException could not create serializer, never thrown with BinarySerializationFormat
* @throws SerializationException Error with the file, could not build entry index
*/
public RandomAccessBinaryList(RandomAccessBinary blob, Binding binding, long startPos, SerializerScheme format)
throws IOException, SerializerConstructionException, SerializationException
{
this.format = format;
this.blob = blob;
this.binding = binding;
serializer = format.getSerializer(binding);
Integer sampleSize = serializer.getConstantSize();
// Variable width sample
if (sampleSize==null)
{
table = new Table(startPos);
blob.position(startPos);
long length = blob.length();
long pos = startPos;
while (posInteger.MAX_VALUE) throw new IllegalArgumentException("The blob is too large");
table = new Constant(startPos, sampleSize, (int) count);
}
}
@Override
public int size() throws RuntimeIOException
{
return table.size()-1;
}
public boolean isOpen()
{
return !closed;
}
/**
* Flushes the caches and closes the file handle.
*/
public void close()
{
synchronized(this) {
if (closed) return;
closed = true;
}
try {
blob.flush();
blob.close();
} catch (IOException ignored) {
}
}
@Override
public Binding getBinding() {
return binding;
}
@SuppressWarnings("unchecked")
@Override
public T get(int index) throws RuntimeIOException {
if (index<0 || index>=size())
throw new IndexOutOfBoundsException();
try {
blob.position( table.get(index) );
identities.clear();
return (T) serializer.deserialize(blob, identities);
} catch (IOException e) {
throw new RuntimeIOException(e);
}
}
public void add(int index, T element) throws RuntimeIOException, RuntimeBindingException {
if (index<0 || index>size())
throw new IndexOutOfBoundsException();
// Append
try {
if (index==size()) {
blob.position( table.get(index) );
identities2.clear();
serializer.serialize(blob, identities2, element);
table.add(blob.position());
modCount++;
blob.flush();
} else
// Insert
{
// Make some room in-file
identities2.clear();
long len = serializer.getSize(element, identities2);
long pos = table.get(index);
blob.flush();
blob.position(pos);
blob.insertBytes(len, ByteSide.Left);
// Write
identities2.clear();
blob.position(pos);
serializer.serialize(blob, identities2, element);
assert(pos+len == blob.position());
blob.flush();
// Update table
table.insert(index, pos);
table.adjust(index+1, table.size(), len);
modCount++;
}
blob.flush();
} catch (IOException e) {
throw new RuntimeIOException(e);
}
};
/**
* Replace the whole content with the content of another collection
*
* @param c collection
* @throws RuntimeIOException
*/
public void setAll(Collection extends T> c) throws RuntimeIOException
{
try {
blob.flush();
if (table.size()>1)
table.remove(1, table.size()-1);
long zerosize = table.get(0);
blob.position(zerosize);
blob.setLength(zerosize);
addAll(c);
blob.flush();
} catch (IOException e) {
throw new RuntimeIOException(e);
}
}
@SuppressWarnings("unchecked")
public T set(int index, T element) throws RuntimeIOException, RuntimeBindingException {
if (index<0 || index>=size())
throw new IndexOutOfBoundsException();
try {
long startPos = table.get(index);
long oldEndPos = table.get(index+1);
long oldSize = oldEndPos - startPos;
// Read old
blob.position( startPos );
identities.clear();
T result = (T) serializer.deserialize(blob, identities);
assert(blob.position() == oldEndPos);
// Calc size of new
identities2.clear();
long newSize = serializer.getSize(element, identities2);
long diff = newSize - oldSize;
if (diff>0) {
blob.flush();
blob.position(startPos);
blob.insertBytes(diff, ByteSide.Left);
} else if (diff<0) {
blob.flush();
blob.position(startPos);
blob.insertBytes(-diff, ByteSide.Left);
}
// Write new
identities2.clear();
blob.position( startPos );
serializer.serialize(blob, identities2, element);
assert(startPos+newSize == blob.position());
blob.flush();
// Update table
table.adjust(index+1, table.size(), diff);
if (diff!=0)
modCount++;
blob.flush();
return result;
} catch (IOException e) {
throw new RuntimeIOException(e);
}
}
@Override
public void removeRange(int fromIndex, int toIndex) throws RuntimeIOException {
if (fromIndex<0 || toIndex<0 || fromIndex>toIndex || toIndex>size())
throw new IndexOutOfBoundsException();
try {
int count = toIndex - fromIndex;
if (count==0) return;
long startPos = table.get(fromIndex);
long endPos = table.get(toIndex);
long length = endPos - startPos;
blob.position(startPos);
blob.removeBytes(length, ByteSide.Left);
table.remove(fromIndex+1, count);
table.adjust(fromIndex+1, table.size(), -length);
modCount++;
blob.flush();
} catch (IOException e) {
throw new RuntimeIOException(e);
}
}
@SuppressWarnings("unchecked")
@Override
public T remove(int index) throws RuntimeIOException {
if (index<0 || index>=size())
throw new IndexOutOfBoundsException();
try {
long startPos = table.get(index);
long endPos = table.get(index+1);
long length = endPos - startPos;
blob.position(startPos);
identities.clear();
T result = (T) serializer.deserialize(blob, identities);
blob.position(startPos);
blob.removeBytes(length, ByteSide.Left);
table.remove(index+1, 1);
table.adjust(index+1, table.size(), -length);
modCount++;
blob.flush();
return result;
} catch (IOException e) {
throw new RuntimeIOException(e);
}
}
@Override
public boolean addAll(Collection extends T> c) throws RuntimeIOException {
return addAll(0, c);
}
@Override
public boolean addAll(int index, Collection extends T> c) throws RuntimeIOException, RuntimeBindingException {
if (index<0 || index>size())
throw new IndexOutOfBoundsException();
// Append
try {
if (index==size()) {
blob.position( table.get(index) );
identities2.clear();
for (T element : c) {
serializer.serialize(blob, identities2, element);
table.add(blob.position());
}
blob.flush();
modCount++;
} else
// Insert
{
// Calc sizes
long startPos = table.get(index);
long endPos = startPos;
int i=0;
for (T element : c) {
identities2.clear();
long len = serializer.getSize(element, identities2);
endPos += len;
i++;
table.insert(index+i, endPos);
}
// Make room
blob.flush();
blob.position(startPos);
blob.insertBytes(endPos-startPos, ByteSide.Left);
// Write
blob.position(startPos);
for (T element : c) {
identities2.clear();
serializer.serialize(blob, identities2, element);
}
blob.flush();
modCount++;
}
blob.flush();
return !c.isEmpty();
} catch (IOException e) {
throw new RuntimeIOException(e);
}
}
interface Index {
long get(int index);
/**
* Get the number of position entries. (= sample count + 1)
* @return
*/
int size();
void add(long position);
void set(int index, long position);
void insert(int index, long position);
void remove(int index, int count);
/**
* Adjust positions
*
* @param fromIndex
* @param toIndex end index (exclusive)
* @param diff position adjustment
*/
void adjust(int fromIndex, int toIndex, long diff);
}
private static class Table implements Index {
/** Table of file positions of each index. There are size() + 1 entries in the table. if null sample size is fixed */
TLongArrayList table = new TLongArrayList(32);
Table(long start) {
table.add(start);
}
@Override
public void add(long position) {
table.add(position);
}
@Override
public void insert(int index, long position) {
table.insert(index, position);
}
@Override
public void set(int index, long position) {
table.set(index, position);
}
@Override
public void remove(int index, int length) {
table.remove(index, length);
}
@Override
public void adjust(int fromIndex, int toIndex, long diff) {
if (diff==0) return;
for (int index = fromIndex; index