/******************************************************************************* * 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 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 c) throws RuntimeIOException { return addAll(0, c); } @Override public boolean addAll(int index, Collection 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