--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2010 Association for Decentralized Information Management in\r
+ * Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.databoard.file;
+
+import gnu.trove.list.array.TLongArrayList;\r
+import gnu.trove.map.hash.TObjectIntHashMap;\r
+\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.util.AbstractList;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.List;\r
+import java.util.RandomAccess;\r
+\r
+import org.simantics.databoard.binding.Binding;\r
+import org.simantics.databoard.binding.error.RuntimeBindingException;\r
+import org.simantics.databoard.serialization.SerializationException;\r
+import org.simantics.databoard.serialization.Serializer;\r
+import org.simantics.databoard.serialization.SerializerConstructionException;\r
+import org.simantics.databoard.serialization.SerializerScheme;\r
+import org.simantics.databoard.util.binary.RandomAccessBinary;\r
+import org.simantics.databoard.util.binary.RandomAccessBinary.ByteSide;\r
+
+/**
+ * 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.<p>
+ *
+ * Each operation may throw {@link RuntimeIOException}, if there is IOException
+ * in the {@link RandomAccessBinary}<p>
+ *
+ * Entry position index is on open if the file has variable width
+ * data type (eg. String). The entire file is scanned through.<p>
+ *
+ * TODO lazy index. Append alone (add()) doesn't require scan.
+ *
+ * @see FileList File based implementation
+ * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
+ */
+public class RandomAccessBinaryList<T> extends AbstractList<T> implements IFileList<T>, RandomAccess {
+
+ /** Reader */
+ RandomAccessBinary blob;
+
+ /** Format */
+ SerializerScheme format;
+
+ /** Binding */
+ Binding binding;
+
+ /** Serializer */
+ Serializer serializer;
+
+ /** Offset table */
+ Index table;
+
+ /** identities */
+ List<Object> identities = new ArrayList<Object>();
+
+ /** identities */
+ TObjectIntHashMap<Object> identities2 = new TObjectIntHashMap<Object>();
+
+ /** 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 (pos<length)
+ {
+ serializer.skip(blob);
+ pos = blob.position();
+ table.add(pos);
+ }
+ } else
+ // Fixed width sample
+ {
+ long fileSize = blob.length();
+ long count = (fileSize - startPos) / sampleSize;
+ if (count>Integer.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<toIndex; index++)
+ table.set(index, table.get(index) + diff);
+ }
+
+ @Override
+ public long get(int index) {
+ return table.get(index);
+ }
+
+ @Override
+ public int size() {
+ return table.size();
+ }
+
+ }
+
+ private static class Constant implements Index {
+ long start;
+ long sampleSize;
+ int count;
+ Constant(long start, int sampleSize, int count) {
+ this.start = start;
+ this.sampleSize = sampleSize;
+ this.count = count;
+ }
+ @Override
+ public void add(long position) {
+ assert( (position-start) % sampleSize == 0);
+ count++;
+ }
+ @Override
+ public void set(int index, long position) {
+ assert( position == start + index * sampleSize);
+ }
+ @Override
+ public void adjust(int fromIndex, int toIndex, long diff) {
+ }
+ @Override
+ public long get(int index) {
+ return start + index*sampleSize;
+ }
+ @Override
+ public void insert(int index, long position) {
+ assert( position == start + index * sampleSize );
+ count++;
+ }
+ @Override
+ public void remove(int index, int count) {
+ this.count -= count;
+ }
+ @Override
+ public int size() {
+ return count+1;
+ }
+ }
+
+ @Override
+ public File getFile() {
+ return null;
+ }
+
+
+}
+
+