Bug 485361 - OperationEmitter should stream read operations

Change-Id: Id35d5774e1d4b74c5353f8d95de319cf73c0fafe
diff --git a/bundles/org.eclipse.emf.emfstore.server.model/src/org/eclipse/emf/emfstore/internal/server/model/versioning/impl/FileBasedChangePackageImpl.java b/bundles/org.eclipse.emf.emfstore.server.model/src/org/eclipse/emf/emfstore/internal/server/model/versioning/impl/FileBasedChangePackageImpl.java
index 013fca3..05fdee3 100644
--- a/bundles/org.eclipse.emf.emfstore.server.model/src/org/eclipse/emf/emfstore/internal/server/model/versioning/impl/FileBasedChangePackageImpl.java
+++ b/bundles/org.eclipse.emf.emfstore.server.model/src/org/eclipse/emf/emfstore/internal/server/model/versioning/impl/FileBasedChangePackageImpl.java
@@ -55,7 +55,6 @@
 import org.eclipse.emf.emfstore.internal.server.model.versioning.impl.persistent.Direction;
 import org.eclipse.emf.emfstore.internal.server.model.versioning.impl.persistent.FileBasedOperationIterable;
 import org.eclipse.emf.emfstore.internal.server.model.versioning.impl.persistent.OperationEmitter;
-import org.eclipse.emf.emfstore.internal.server.model.versioning.impl.persistent.ReadLineCapable;
 import org.eclipse.emf.emfstore.internal.server.model.versioning.impl.persistent.XmlTags;
 import org.eclipse.emf.emfstore.internal.server.model.versioning.operations.AbstractOperation;
 import org.eclipse.emf.emfstore.server.ESCloseableIterable;
@@ -63,6 +62,7 @@
 
 import com.google.common.base.Optional;
 import com.google.common.collect.Iterables;
+import com.google.common.io.Closeables;
 
 /**
  * <!-- begin-user-doc -->
@@ -93,7 +93,7 @@
 	/**
 	 * @generated NOT
 	 */
-	private static final String EMPTY_CHANGE_PACKAGE = XmlTags.XML_HEADER + XmlTags.CHANGE_PACKAGE_START
+	private static final String EMPTY_CHANGE_PACKAGE = XmlTags.XML_HEADER + XmlTags.CHANGE_PACKAGE_START_WITH_NEWLINE
 		+ XmlTags.CHANGE_PACKAGE_END;
 
 	/**
@@ -687,16 +687,15 @@
 	 */
 	public List<AbstractOperation> removeAtEnd(int n) {
 		final List<AbstractOperation> ops = new ArrayList<AbstractOperation>();
-		final OperationEmitter operationEmitter = new OperationEmitter(Direction.Backward);
-		Optional<ReversedLinesFileReader> maybeReversedReader = Optional.absent();
-		int counter = n;
+		final Optional<ReversedLinesFileReader> maybeReversedReader = Optional.absent();
+		RandomAccessFile raf = null;
+		OperationEmitter operationEmitter = null;
 
 		try {
-			final ReversedLinesFileReader reversedReader = new ReversedLinesFileReader(new File(getTempFilePath()));
-			maybeReversedReader = Optional.of(reversedReader);
+			operationEmitter = new OperationEmitter(Direction.Backward, new File(getTempFilePath()));
+			int counter = n;
 			AbstractOperation operation;
-			final ReadLineCapable reader = ReadLineCapable.INSTANCE.create(reversedReader);
-			final Optional<AbstractOperation> maybeOperation = operationEmitter.tryEmit(reader);
+			final Optional<AbstractOperation> maybeOperation = operationEmitter.tryEmit();
 
 			int removedOps = 0;
 			int removedLeafOps = 0;
@@ -710,16 +709,12 @@
 
 			updateCaches(-removedOps, -removedLeafOps);
 
-			// FIXME: reuse ReadLineCapable?
-			final long offset = operationEmitter.getOffset();
-
-			final RandomAccessFile raf = new RandomAccessFile(getTempFilePath(), "rw"); //$NON-NLS-1$
-			final long skip = raf.length() + 1 - offset;
+			raf = new RandomAccessFile(getTempFilePath(), "rw"); //$NON-NLS-1$
+			final long skip = operationEmitter.getOffset();
 			raf.seek(skip);
 			final byte[] bytes = asBytes(XmlTags.NEWLINE + XmlTags.CHANGE_PACKAGE_END);
 			raf.write(bytes);
-			raf.setLength(skip + bytes.length);
-			raf.close();
+			raf.setLength(skip + bytes.length);			
 
 			return ops;
 
@@ -727,12 +722,14 @@
 			// ESException not available
 			throw new IllegalStateException(ex);
 		} finally {
-			if (maybeReversedReader.isPresent()) {
-				try {
-					maybeReversedReader.get().close();
-				} catch (final IOException ex) {
-					ModelUtil.logException(ex);
+			try {
+				Closeables.close(raf, true);
+				Closeables.close(operationEmitter, true);
+				if (maybeReversedReader.isPresent()) {
+					Closeables.close(maybeReversedReader.get(), true);
 				}
+			} catch (final IOException ex) {
+				ModelUtil.logException(ex);
 			}
 		}
 	}
diff --git a/bundles/org.eclipse.emf.emfstore.server.model/src/org/eclipse/emf/emfstore/internal/server/model/versioning/impl/persistent/OperationEmitter.java b/bundles/org.eclipse.emf.emfstore.server.model/src/org/eclipse/emf/emfstore/internal/server/model/versioning/impl/persistent/OperationEmitter.java
index f056d89..99bbc7f 100644
--- a/bundles/org.eclipse.emf.emfstore.server.model/src/org/eclipse/emf/emfstore/internal/server/model/versioning/impl/persistent/OperationEmitter.java
+++ b/bundles/org.eclipse.emf.emfstore.server.model/src/org/eclipse/emf/emfstore/internal/server/model/versioning/impl/persistent/OperationEmitter.java
@@ -11,23 +11,33 @@
  ******************************************************************************/
 package org.eclipse.emf.emfstore.internal.server.model.versioning.impl.persistent;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
+import java.io.BufferedReader;
+import java.io.Closeable;
+import java.io.DataInput;
+import java.io.File;
+import java.io.FileReader;
 import java.io.IOException;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.io.RandomAccessFile;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 
-import org.apache.commons.lang.StringUtils;
+import org.apache.commons.io.input.ReversedLinesFileReader;
 import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.EObject;
 import org.eclipse.emf.ecore.resource.Resource;
 import org.eclipse.emf.ecore.resource.ResourceSet;
 import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
+import org.eclipse.emf.ecore.xmi.XMLResource;
+import org.eclipse.emf.ecore.xmi.impl.XMLHelperImpl;
+import org.eclipse.emf.ecore.xmi.impl.XMLLoadImpl;
+import org.eclipse.emf.ecore.xmi.impl.XMLResourceImpl;
 import org.eclipse.emf.emfstore.internal.common.model.util.ModelUtil;
 import org.eclipse.emf.emfstore.internal.server.model.versioning.operations.AbstractOperation;
-import org.eclipse.emf.emfstore.internal.server.model.versioning.operations.CreateDeleteOperation;
 
 import com.google.common.base.Optional;
+import com.google.common.collect.Maps;
 
 /**
  * Type for emitting {@link AbstractOperation}s when given an {@link ReadLineCapable} type.
@@ -35,23 +45,67 @@
  * @author emueller
  *
  */
-public class OperationEmitter {
+public class OperationEmitter implements Closeable {
 
-	private static final long NEWLINE_LENGTH = System.getProperty("line.separator").getBytes().length; //$NON-NLS-1$
-
-	private boolean withinOperationsElement;
 	private final Direction direction;
-	private long offset;
+
+	private final File operationsFile;
+
+	private ReadLineCapable reader;
+	private final List<Long> forwardOffsets = new ArrayList<Long>();
+	private final List<Long> backwardsOffsets = new ArrayList<Long>();
+	private int currentOpIndex;
+	private long startOffset;
 
 	/**
 	 * Constructor.
 	 *
 	 * @param direction
 	 *            the {@link Direction} that is used for reading
+	 * @param file
+	 *            the operation file
 	 */
-	public OperationEmitter(Direction direction) {
+	public OperationEmitter(Direction direction, File file) {
 		this.direction = direction;
-		offset = 0;
+		operationsFile = file;
+		determineOperationOffsets();
+		currentOpIndex = direction == Direction.Forward ? 0 : backwardsOffsets.size() - 1;
+		initReader();
+	}
+
+	private void determineOperationOffsets() {
+		try {
+			// TODO: are there any alternative implementations (e.g. Apache)?
+			final OptimizedRandomAccessFile raf = new OptimizedRandomAccessFile(operationsFile, "r"); //$NON-NLS-1$
+			try {
+				String line;
+				while ((line = raf.readLine()) != null) {
+					if (line.contains(XmlTags.CHANGE_PACKAGE_START)) {
+						startOffset = raf.getFilePointer();
+					} else if (line.contains(XmlTags.OPERATIONS_START_TAG)) {
+						forwardOffsets.add(raf.getFilePointer());
+					} else if (line.contains(XmlTags.OPERATIONS_END_TAG)) {
+						backwardsOffsets.add(raf.getFilePointer());
+					}
+				}
+			} finally {
+				raf.close();
+			}
+		} catch (final IOException ex) {
+			ModelUtil.logException(ex);
+		}
+	}
+
+	private void initReader() {
+		try {
+			if (direction == Direction.Forward) {
+				reader = ReadLineCapable.INSTANCE.create(new BufferedReader(new FileReader(operationsFile)));
+			} else {
+				reader = ReadLineCapable.INSTANCE.create(new ReversedLinesFileReader(operationsFile));
+			}
+		} catch (final IOException ex) {
+			ModelUtil.logException(ex);
+		}
 	}
 
 	/**
@@ -60,45 +114,125 @@
 	 * @return the current offset
 	 */
 	public long getOffset() {
-		return offset;
+		if (currentOpIndex < 0) {
+			return startOffset;
+		}
+		return backwardsOffsets.get(currentOpIndex);
+	}
+
+	private void readForward(PipedOutputStream pos) {
+		try {
+			boolean withinOperationsElement = false;
+			final boolean isForwardDir = direction == Direction.Forward;
+			final String closingTag = getClosingTag(isForwardDir);
+			String line = reader.readLine();
+			while (line != null && !line.contains(closingTag)) {
+				if (line.contains(getOpeningTag(isForwardDir))) {
+					withinOperationsElement = true;
+				} else if (withinOperationsElement) {
+					pos.write(line.getBytes());
+				}
+				line = reader.readLine();
+			}
+			if (line != null) {
+				withinOperationsElement = false;
+			}
+		} catch (final IOException ex) {
+			ModelUtil.logException(ex);
+		} finally {
+			try {
+				pos.close();
+			} catch (final IOException ex) {
+				ModelUtil.logException(ex);
+			}
+		}
+	}
+
+	private void readForward(DataInput reader, PipedOutputStream pos) {
+		try {
+			boolean withinOperationsElement = true;
+			final String closingTag = getClosingTag(true);
+			String line = reader.readLine();
+			while (line != null && !line.contains(closingTag)) {
+				if (line.contains(getOpeningTag(true))) {
+					withinOperationsElement = true;
+				} else if (withinOperationsElement && line.length() > 0) {
+					pos.write(line.getBytes());
+				}
+				line = reader.readLine();
+			}
+		} catch (final IOException ex) {
+			ModelUtil.logException(ex);
+		} finally {
+			try {
+				pos.close();
+			} catch (final IOException ex) {
+				ModelUtil.logException(ex);
+			}
+		}
+	}
+
+	private void readBackward(PipedOutputStream pos) {
+
+		if (currentOpIndex < 0) {
+			try {
+				pos.close();
+			} catch (final IOException ex) {
+				ModelUtil.logException(ex);
+			}
+			return;
+		}
+
+		final long offset = forwardOffsets.get(currentOpIndex);
+		currentOpIndex -= 1;
+
+		RandomAccessFile raf = null;
+		try {
+			raf = new RandomAccessFile(operationsFile, "r"); //$NON-NLS-1$
+			raf.skipBytes((int) offset);
+			readForward(raf, pos);
+		} catch (final IOException ex) {
+			ModelUtil.logException(ex);
+		} finally {
+			try {
+				raf.close();
+			} catch (final IOException ex) {
+				ModelUtil.logException(ex);
+			}
+		}
 	}
 
 	/**
-	 * Given a reader, tries to parse an operation and emit it is,
+	 * Tries to parse an operation in the reading directions and emits it,
 	 * if parsing has been successful.
 	 *
-	 * @param reader
-	 *            the reader that is used to de-serialize operations
 	 * @return the successfully parsed operation
 	 * @throws IOException
 	 *             in case reading from the {@link ReadLineCapable} fails
 	 */
-	public Optional<AbstractOperation> tryEmit(ReadLineCapable reader) throws IOException {
-		final List<String> readLines = new ArrayList<String>();
-		withinOperationsElement = false;
-		String line;
-		final boolean isForwardDir = direction == Direction.Forward;
-		while ((line = reader.readLine()) != null && !line.contains(getClosingTag(isForwardDir))) {
-			if (line.contains(getOpeningTag(isForwardDir))) {
-				withinOperationsElement = true;
-			} else if (withinOperationsElement) {
-				readLines.add(line);
+	public Optional<AbstractOperation> tryEmit() throws IOException {
+		final PipedOutputStream pos = new PipedOutputStream();
+		final PipedInputStream pis = new PipedInputStream(pos);
+
+		new Thread(new Runnable() {
+			public void run() {
+				if (direction == Direction.Forward) {
+					readForward(pos);
+				} else {
+					readBackward(pos);
+				}
 			}
-			offset += line.getBytes().length;
-			offset += NEWLINE_LENGTH;
-		}
-		if (line != null) {
-			withinOperationsElement = false;
-			offset += line.getBytes().length;
-		}
-		if (!withinOperationsElement && !readLines.isEmpty()) {
-			if (direction == Direction.Backward) {
-				Collections.reverse(readLines);
-			}
-			return Optional.of(deserialize(StringUtils.join(readLines, StringUtils.EMPTY)));
+		}).start();
+
+		try {
+			return Optional.of(deserialize(pis));
+		} catch (final IOException e) {
+			// e.printStackTrace();
+			return Optional.absent();
+		} finally {
+			pis.close();
 		}
 
-		return Optional.absent();
 	}
 
 	private String getClosingTag(boolean isForward) {
@@ -109,18 +243,25 @@
 		return isForward ? XmlTags.OPERATIONS_START_TAG : XmlTags.OPERATIONS_END_TAG;
 	}
 
-	private AbstractOperation deserialize(final String string) throws IOException {
+	private AbstractOperation deserialize(final PipedInputStream pis) throws IOException {
 		final ResourceSet resourceSet = new ResourceSetImpl();
 		final Resource resource = resourceSet.createResource(URI.createURI("virtualResource.xmi")); //$NON-NLS-1$
-		final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-		outputStream.write(string.getBytes());
-		final ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
-		resource.load(inputStream, ModelUtil.getResourceLoadOptions());
+		((XMLResourceImpl) resource).setIntrinsicIDToEObjectMap(Maps.<String, EObject> newLinkedHashMap());
+		final XMLLoadImpl xmlLoadImpl = new XMLLoadImpl(new XMLHelperImpl());
+		xmlLoadImpl.load((XMLResource) resource, pis, ModelUtil.getResourceLoadOptions());
 		final AbstractOperation operation = (AbstractOperation) resource.getContents().get(0);
-		if (operation instanceof CreateDeleteOperation) {
-			((CreateDeleteOperation) operation).getSubOperations();
-		}
+		// ((XMLResourceImpl) resource).getIntrinsicIDToEObjectMap().clear();
 		return operation;
-
 	}
-}
\ No newline at end of file
+
+	/**
+	 * Closes the emitter.
+	 */
+	public void close() {
+		try {
+			reader.close();
+		} catch (final IOException ex) {
+			ModelUtil.logException(ex);
+		}
+	}
+}
diff --git a/bundles/org.eclipse.emf.emfstore.server.model/src/org/eclipse/emf/emfstore/internal/server/model/versioning/impl/persistent/OperationIterator.java b/bundles/org.eclipse.emf.emfstore.server.model/src/org/eclipse/emf/emfstore/internal/server/model/versioning/impl/persistent/OperationIterator.java
index 79597dd..f3c3448 100644
--- a/bundles/org.eclipse.emf.emfstore.server.model/src/org/eclipse/emf/emfstore/internal/server/model/versioning/impl/persistent/OperationIterator.java
+++ b/bundles/org.eclipse.emf.emfstore.server.model/src/org/eclipse/emf/emfstore/internal/server/model/versioning/impl/persistent/OperationIterator.java
@@ -11,13 +11,10 @@
  ******************************************************************************/
 package org.eclipse.emf.emfstore.internal.server.model.versioning.impl.persistent;
 
-import java.io.BufferedReader;
 import java.io.File;
-import java.io.FileReader;
 import java.io.IOException;
 import java.util.Iterator;
 
-import org.apache.commons.io.input.ReversedLinesFileReader;
 import org.apache.commons.lang.NotImplementedException;
 import org.eclipse.emf.emfstore.internal.server.model.versioning.operations.AbstractOperation;
 
@@ -34,7 +31,7 @@
 
 	private Optional<AbstractOperation> operation;
 	private OperationEmitter operationEmitter;
-	private ReadLineCapable reader;
+	// private ReadLineCapable reader;
 	private boolean isInitialized;
 	private final String operationsFilePath;
 	private final Direction direction;
@@ -54,17 +51,7 @@
 	}
 
 	private void init() {
-		operationEmitter = new OperationEmitter(direction);
-		try {
-			if (direction == Direction.Forward) {
-				reader = ReadLineCapable.INSTANCE
-					.create(new BufferedReader(new FileReader(new File(operationsFilePath))));
-			} else {
-				reader = ReadLineCapable.INSTANCE.create(new ReversedLinesFileReader(new File(operationsFilePath)));
-			}
-		} catch (final IOException ex1) {
-			ex1.printStackTrace();
-		}
+		operationEmitter = new OperationEmitter(direction, new File(operationsFilePath));
 		isInitialized = true;
 	}
 
@@ -79,13 +66,14 @@
 			init();
 		}
 		try {
-			operation = operationEmitter.tryEmit(reader);
+			operation = operationEmitter.tryEmit();
 			final boolean hasNext = operation.isPresent();
 			if (!hasNext) {
 				close();
 			}
 			return hasNext;
 		} catch (final IOException ex) {
+			// TODO
 			// replace operations file
 			ex.printStackTrace();
 		}
@@ -120,13 +108,6 @@
 	 * Closes the underlying operations file.
 	 */
 	public void close() {
-		try {
-			if (reader != null) {
-				reader.close();
-			}
-		} catch (final IOException ex) {
-			// TODO
-			ex.printStackTrace();
-		}
+		operationEmitter.close();
 	}
 }
diff --git a/bundles/org.eclipse.emf.emfstore.server.model/src/org/eclipse/emf/emfstore/internal/server/model/versioning/impl/persistent/OptimizedRandomAccessFile.java b/bundles/org.eclipse.emf.emfstore.server.model/src/org/eclipse/emf/emfstore/internal/server/model/versioning/impl/persistent/OptimizedRandomAccessFile.java
new file mode 100644
index 0000000..3ab37e6
--- /dev/null
+++ b/bundles/org.eclipse.emf.emfstore.server.model/src/org/eclipse/emf/emfstore/internal/server/model/versioning/impl/persistent/OptimizedRandomAccessFile.java
@@ -0,0 +1,1088 @@
+/*
+ * The MIT License
+ * Copyright 2013 Joos Kiener <Joos.Kiener@gmail.com>.
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.eclipse.emf.emfstore.internal.server.model.versioning.impl.persistent;
+
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.io.UTFDataFormatException;
+
+/**
+ * <p>
+ * Wrapper of {@link java.io.RandomAccessFile
+ * <code>RandomAccessFile</code>} that has an readLine method performing similar
+ * to {@link java.io.BufferedReader#readLine()} while keeping the random access
+ * functionality.
+ * </p>
+ *
+ * <p>
+ * {@link java.io.RandomAccessFile#readLine()} is very slow as it reads a
+ * file byte by byte. This here will perform 2 orders of magnitude faster.
+ * For thread-safety all read and write methods are synchronized.
+ * </p>
+ * </p>
+ * If the underlying {@link java.nio.channels.FileChannel
+ * <code>FileChannel</code>} is manipulated, the behavior is unpredictable.
+ * Therefore this class does not expose the <code>getFileChannel()</code> method
+ * of <code>RandomAccessFile</code>.
+ * </p>
+ *
+ * @author Joos Kiener <Joos.Kiener@gmail.com>
+ */
+public class OptimizedRandomAccessFile {
+
+	private static final int BUFFER_SIZE = 8192;
+	private static int defaultExpectedLineLength = 80;
+	private final RandomAccessFile raf;
+	private Long actualFilePointer;
+	private final char[] charBuffer;
+	private int nChars, nextChar;
+	private final int bufferSize;
+	private long lastOffset;
+	private boolean skipLF;
+
+	/**
+	 * see {@link RandomAccessFile#RandomAccessFile(String,String)}
+	 *
+	 * @param name path to the text file
+	 * @param mode r, rw, rws, rwd
+	 * @throws FileNotFoundException
+	 */
+	public OptimizedRandomAccessFile(String name, String mode) throws FileNotFoundException {
+		this(name != null ? new File(name) : null, mode);
+	}
+
+	/**
+	 *
+	 * see {@link RandomAccessFile#RandomAccessFile(File,String)}
+	 *
+	 * @param file
+	 * @param mode
+	 * @throws FileNotFoundException
+	 */
+	public OptimizedRandomAccessFile(File file, String mode) throws FileNotFoundException {
+		raf = new RandomAccessFile(file, mode);
+		actualFilePointer = null;
+		bufferSize = BUFFER_SIZE;
+		charBuffer = new char[bufferSize];
+	}
+
+	/**
+	 * <p>
+	 * Returns the opaque file descriptor object associated with this
+	 * stream.
+	 * </p>
+	 *
+	 * @return the file descriptor object associated with this stream.
+	 * @exception IOException if an I/O error occurs.
+	 * @see java.io.FileDescriptor
+	 */
+	public final FileDescriptor getFD() throws IOException {
+		return raf.getFD();
+	}
+
+	/**
+	 * Reads a byte of data from this file. The byte is returned as an integer
+	 * in the range 0 to 255 (
+	 * <code>0x00-0x0ff</code>). This method behaves similar to the
+	 * {@link java.io.BufferedReader#read()} method of
+	 * <code>BufferedReader</code>.
+	 *
+	 * @return the next byte of data, or <code>-1</code> if the end of the file
+	 *         has been reached.
+	 * @exception IOException if an I/O error occurs. Not thrown if end-of-file
+	 *                has been reached.
+	 */
+	public synchronized int read() throws IOException {
+		// resetPosition();
+		for (;;) {
+			if (nextChar >= nChars) {
+				fill();
+				if (nextChar >= nChars) {
+					return -1;
+				}
+			}
+			if (skipLF) {
+				skipLF = false;
+				if (charBuffer[nextChar] == '\n') {
+					nextChar++;
+					continue;
+				}
+			}
+			final int result = charBuffer[nextChar++];
+			actualFilePointer++;
+			return result;
+		}
+	}
+
+	/**
+	 * Reads characters into a portion of an array, reading from the underlying
+	 * stream if necessary.
+	 */
+	private int read1(byte[] cbuf, int off, int len) throws IOException {
+		if (nextChar >= nChars) {
+			/*
+			 * If the requested length is at least as large as the buffer and
+			 * if line feeds are not being skipped, do not bother to copy the
+			 * characters into the local buffer. In this way buffered streams
+			 * will cascade harmlessly.
+			 */
+			if (len >= charBuffer.length && !skipLF) {
+				actualFilePointer = null;
+				return raf.read(cbuf, off, len);
+			}
+			fill();
+		}
+		if (nextChar >= nChars) {
+			return -1;
+		}
+		if (skipLF) {
+			skipLF = false;
+			if (charBuffer[nextChar] == '\n') {
+				nextChar++;
+				if (nextChar >= nChars) {
+					fill();
+				}
+				if (nextChar >= nChars) {
+					return -1;
+				}
+			}
+		}
+		final int n = Math.min(len, nChars - nextChar);
+		for (int i = 0; i < n; i++) {
+			cbuf[off + i] = (byte) charBuffer[nextChar + i];
+		}
+		// System.arraycopy(charBuffer, nextChar, cbuf, off, n);
+		nextChar += n;
+		actualFilePointer += n;
+		return n;
+	}
+
+	/**
+	 * Reads up to
+	 * <code>len</code> bytes of data from this file into an array of bytes.
+	 * This method behaves similar to the
+	 * {@link java.io.BufferedReader#read(byte[],int,int)} method of
+	 * <code>BufferedReader</code>.
+	 *
+	 * @param b the buffer into which the data is read.
+	 * @param off the start offset in array <code>b</code> at which the data is
+	 *            written.
+	 * @param len the maximum number of bytes read.
+	 * @return the total number of bytes read into the buffer, or
+	 *         <code>-1</code> if there is no more data because the end of the file has
+	 *         been reached.
+	 * @exception IOException If the first byte cannot be read for any reason
+	 *                other than end of file, or if the random access file has been closed, or
+	 *                if some other I/O error occurs.
+	 * @exception NullPointerException If <code>b</code> is <code>null</code>.
+	 * @exception IndexOutOfBoundsException If <code>off</code> is negative,
+	 *                <code>len</code> is negative, or <code>len</code> is greater than
+	 *                <code>b.length - off</code>
+	 */
+	public synchronized int read(byte b[], int off, int len) throws IOException {
+		// resetPosition();
+		if (off < 0 || off > b.length || len < 0 || off + len > b.length || off + len < 0) {
+			throw new IndexOutOfBoundsException();
+		} else if (len == 0) {
+			return 0;
+		}
+
+		int n = read1(b, off, len);
+		if (n <= 0) {
+			return n;
+		}
+		while (n < len) {
+			final int n1 = read1(b, off + n, len - n);
+			if (n1 <= 0) {
+				break;
+			}
+			n += n1;
+		}
+		return n;
+		// return raf.read(b, off, len);
+	}
+
+	/**
+	 * Reads up to
+	 * <code>b.length</code> bytes of data from this file into an array of
+	 * bytes. This method behaves similar to the
+	 * {@link java.io.BufferedReader#read(byte[])} method of
+	 * <code>BufferedReader</code>.
+	 *
+	 * @param b the buffer into which the data is read.
+	 * @return the total number of bytes read into the buffer, or
+	 *         <code>-1</code> if there is no more data because the end of this file has
+	 *         been reached.
+	 * @exception IOException If the first byte cannot be read for any reason
+	 *                other than end of file, or if the random access file has been closed, or
+	 *                if some other I/O error occurs.
+	 * @exception NullPointerException If <code>b</code> is <code>null</code>.
+	 */
+	public synchronized int read(byte b[]) throws IOException {
+		return read(b, 0, b.length);
+	}
+
+	/**
+	 * Reads
+	 * <code>b.length</code> bytes from this file into the byte array, starting
+	 * at the current file pointer. This method reads repeatedly from the file
+	 * until the requested number of bytes are read. This method blocks until
+	 * the requested number of bytes are read, the end of the stream is
+	 * detected, or an exception is thrown.
+	 *
+	 * @param b the buffer into which the data is read.
+	 * @exception EOFException if this file reaches the end before reading all
+	 *                the bytes.
+	 * @exception IOException if an I/O error occurs.
+	 */
+	public synchronized final void readFully(byte b[]) throws IOException {
+		resetPosition();
+		raf.readFully(b);
+	}
+
+	/**
+	 * Reads exactly
+	 * <code>len</code> bytes from this file into the byte array, starting at
+	 * the current file pointer. This method reads repeatedly from the file
+	 * until the requested number of bytes are read. This method blocks until
+	 * the requested number of bytes are read, the end of the stream is
+	 * detected, or an exception is thrown.
+	 *
+	 * @param b the buffer into which the data is read.
+	 * @param off the start offset of the data.
+	 * @param len the number of bytes to read.
+	 * @exception EOFException if this file reaches the end before reading all
+	 *                the bytes.
+	 * @exception IOException if an I/O error occurs.
+	 */
+	public synchronized final void readFully(byte b[], int off, int len) throws IOException {
+		resetPosition();
+		raf.readFully(b, off, len);
+	}
+
+	/**
+	 * Attempts to skip over
+	 * <code>n</code> bytes of input discarding the skipped bytes.
+	 * <p>
+	 *
+	 * This method may skip over some smaller number of bytes, possibly zero.
+	 * This may result from any of a number of conditions; reaching end of file
+	 * before
+	 * <code>n</code> bytes have been skipped is only one possibility. This
+	 * method never throws an
+	 * <code>EOFException</code>. The actual number of bytes skipped is
+	 * returned. If
+	 * <code>n</code> is negative, no bytes are skipped.
+	 *
+	 * @param n the number of bytes to be skipped.
+	 * @return the actual number of bytes skipped.
+	 * @exception IOException if an I/O error occurs.
+	 */
+	public synchronized int skipBytes(int n) throws IOException {
+
+		if (n < 0L) {
+			throw new IllegalArgumentException("skip value is negative");
+		}
+
+		int r = n;
+		while (r > 0) {
+			if (nextChar >= nChars) {
+				fill();
+			}
+			if (nextChar >= nChars) /* EOF */ {
+				break;
+			}
+			if (skipLF) {
+				skipLF = false;
+				if (charBuffer[nextChar] == '\n') {
+					nextChar++;
+				}
+			}
+			final long d = nChars - nextChar;
+			if (r <= d) {
+				nextChar += r;
+				r = 0;
+				break;
+			} else {
+				r -= d;
+				nextChar = nChars;
+			}
+		}
+		final int skipped = n - r;
+		actualFilePointer += skipped;
+		return skipped;
+
+		// resetPosition();
+		// return raf.skipBytes(n);
+	}
+
+	// 'Write' primitives
+	/**
+	 * Writes the specified byte to this file. The write starts at the current
+	 * file pointer.
+	 *
+	 * @param b the <code>byte</code> to be written.
+	 * @exception IOException if an I/O error occurs.
+	 */
+	public synchronized void write(int b) throws IOException {
+		resetPosition();
+		raf.write(b);
+	}
+
+	/**
+	 * Writes
+	 * <code>b.length</code> bytes from the specified byte array to this file,
+	 * starting at the current file pointer.
+	 *
+	 * @param b the data.
+	 * @exception IOException if an I/O error occurs.
+	 */
+	public synchronized void write(byte b[]) throws IOException {
+		resetPosition();
+		raf.write(b, 0, b.length);
+	}
+
+	/**
+	 * Writes
+	 * <code>len</code> bytes from the specified byte array starting at offset
+	 * <code>off</code> to this file.
+	 *
+	 * @param b the data.
+	 * @param off the start offset in the data.
+	 * @param len the number of bytes to write.
+	 * @exception IOException if an I/O error occurs.
+	 */
+	public synchronized void write(byte b[], int off, int len) throws IOException {
+		resetPosition();
+		raf.write(b, off, len);
+	}
+
+	// 'Random access' stuff
+	/**
+	 * Returns the current offset in this file.
+	 *
+	 * @return the offset from the beginning of the file, in bytes, at which the
+	 *         next read or write occurs.
+	 * @exception IOException if an I/O error occurs.
+	 */
+	public synchronized long getFilePointer() throws IOException {
+		if (actualFilePointer == null) {
+			return raf.getFilePointer();
+		} else {
+			return actualFilePointer;
+		}
+	}
+
+	/**
+	 * Sets the file-pointer offset, measured from the beginning of this file,
+	 * at which the next read or write occurs. The offset may be set beyond the
+	 * end of the file. Setting the offset beyond the end of the file does not
+	 * change the file length. The file length will change only by writing after
+	 * the offset has been set beyond the end of the file.
+	 *
+	 * @param pos the offset position, measured in bytes from the beginning of
+	 *            the file, at which to set the file pointer.
+	 * @exception IOException if <code>pos</code> is less than <code>0</code> or
+	 *                if an I/O error occurs.
+	 */
+	public synchronized void seek(long pos) throws IOException {
+		actualFilePointer = null;
+		resetPosition();
+		raf.seek(pos);
+	}
+
+	/**
+	 * Returns the length of this file.
+	 *
+	 * @return the length of this file, measured in bytes.
+	 * @exception IOException if an I/O error occurs.
+	 */
+	public synchronized long length() throws IOException {
+		return raf.length();
+	}
+
+	/**
+	 * Sets the length of this file.
+	 *
+	 * <p>
+	 * If the present length of the file as returned by the
+	 * <code>length</code> method is greater than the
+	 * <code>newLength</code> argument then the file will be truncated. In this
+	 * case, if the file offset as returned by the
+	 * <code>getFilePointer</code> method is greater than
+	 * <code>newLength</code> then after this method returns the offset will be
+	 * equal to
+	 * <code>newLength</code>.
+	 *
+	 * <p>
+	 * If the present length of the file as returned by the
+	 * <code>length</code> method is smaller than the
+	 * <code>newLength</code> argument then the file will be extended. In this
+	 * case, the contents of the extended portion of the file are not defined.
+	 *
+	 * @param newLength The desired length of the file
+	 * @exception IOException If an I/O error occurs
+	 * @since 1.2
+	 */
+	public synchronized void setLength(long newLength) throws IOException {
+		if (newLength < raf.length()) {
+			resetPosition();
+		}
+		raf.setLength(newLength);
+	}
+
+	/**
+	 * Closes this random access file stream and releases any system resources
+	 * associated with the stream. A closed random access file cannot perform
+	 * input or output operations and cannot be reopened.
+	 *
+	 * <p>
+	 * If this file has an associated channel then the channel is closed as
+	 * well.
+	 *
+	 * @exception IOException if an I/O error occurs.
+	 *
+	 * @revised 1.4
+	 * @spec JSR-51
+	 */
+	public void close() throws IOException {
+		raf.close();
+	}
+
+	//
+	// Some "reading/writing Java data types" methods stolen from
+	// DataInputStream and DataOutputStream.
+	//
+	/**
+	 * Reads a
+	 * <code>boolean</code> from this file. This method reads a single byte from
+	 * the file, starting at the current file pointer. A value of
+	 * <code>0</code> represents
+	 * <code>false</code>. Any other value represents
+	 * <code>true</code>. This method blocks until the byte is read, the end of
+	 * the stream is detected, or an exception is thrown.
+	 *
+	 * @return the <code>boolean</code> value read.
+	 * @exception EOFException if this file has reached the end.
+	 * @exception IOException if an I/O error occurs.
+	 */
+	public synchronized final boolean readBoolean() throws IOException {
+		resetPosition();
+		return raf.readBoolean();
+	}
+
+	/**
+	 * Reads a signed eight-bit value from this file. This method reads a byte
+	 * from the file, starting from the current file pointer. If the byte read
+	 * is
+	 * <code>b</code>, where
+	 * <code>0&nbsp;&lt;=&nbsp;b&nbsp;&lt;=&nbsp;255</code>, then the result is:
+	 * null null null null null null null null null null null null null null <blockquote>
+	 * 
+	 * <pre>
+	 * (byte) (b)
+	 * </pre>
+	 * 
+	 * </blockquote>
+	 * <p>
+	 * This method blocks until the byte is read, the
+	 * end of the stream is detected, or an exception is thrown.
+	 *
+	 * @return the next byte of this file as a signed eight-bit
+	 *         <code>byte</code>.
+	 * @exception EOFException if this file has reached the end.
+	 * @exception IOException if an I/O error occurs.
+	 */
+	public synchronized final byte readByte() throws IOException {
+		resetPosition();
+		return raf.readByte();
+	}
+
+	/**
+	 * Reads an unsigned eight-bit number from this file. This method reads a
+	 * byte from this file, starting at the current file pointer, and returns
+	 * that byte.
+	 * <p>
+	 * This method blocks until the byte is read, the end of the
+	 * stream is detected, or an exception is thrown.
+	 *
+	 * @return the next byte of this file, interpreted as an unsigned eight-bit
+	 *         number.
+	 * @exception EOFException if this file has reached the end.
+	 * @exception IOException if an I/O error occurs.
+	 */
+	public synchronized final int readUnsignedByte() throws IOException {
+		resetPosition();
+		return raf.readUnsignedByte();
+	}
+
+	/**
+	 * Reads a signed 16-bit number from this file. The method reads two bytes
+	 * from this file, starting at the current file pointer. If the two bytes
+	 * read, in order, are
+	 * <code>b1</code> and
+	 * <code>b2</code>, where each of the two values is between
+	 * <code>0</code> and
+	 * <code>255</code>, inclusive, then the result is equal to: null null null
+	 * null null null null null null null null null null null <blockquote>
+	 * 
+	 * <pre>
+	 * (short) ((b1 &lt;&lt; 8) | b2)
+	 * </pre>
+	 * 
+	 * </blockquote>
+	 * <p>
+	 * This method blocks until the two bytes are read,
+	 * the end of the stream is detected, or an exception is thrown.
+	 *
+	 * @return the next two bytes of this file, interpreted as a signed 16-bit
+	 *         number.
+	 * @exception EOFException if this file reaches the end before reading two
+	 *                bytes.
+	 * @exception IOException if an I/O error occurs.
+	 */
+	public synchronized final short readShort() throws IOException {
+		resetPosition();
+		return raf.readShort();
+	}
+
+	/**
+	 * Reads an unsigned 16-bit number from this file. This method reads two
+	 * bytes from the file, starting at the current file pointer. If the bytes
+	 * read, in order, are
+	 * <code>b1</code> and
+	 * <code>b2</code>, where
+	 * <code>0&nbsp;&lt;=&nbsp;b1, b2&nbsp;&lt;=&nbsp;255</code>, then the
+	 * result is equal to: null null null null null null null null null null
+	 * null null null null <blockquote>
+	 * 
+	 * <pre>
+	 * (b1 &lt;&lt; 8) | b2
+	 * </pre>
+	 * 
+	 * </blockquote>
+	 * <p>
+	 * This method blocks until the two bytes are read,
+	 * the end of the stream is detected, or an exception is thrown.
+	 *
+	 * @return the next two bytes of this file, interpreted as an unsigned
+	 *         16-bit integer.
+	 * @exception EOFException if this file reaches the end before reading two
+	 *                bytes.
+	 * @exception IOException if an I/O error occurs.
+	 */
+	public synchronized final int readUnsignedShort() throws IOException {
+		resetPosition();
+		return raf.readUnsignedShort();
+	}
+
+	/**
+	 * Reads a character from this file. This method reads two bytes from the
+	 * file, starting at the current file pointer. If the bytes read, in order,
+	 * are
+	 * <code>b1</code> and
+	 * <code>b2</code>, where
+	 * <code>0&nbsp;&lt;=&nbsp;b1,&nbsp;b2&nbsp;&lt;=&nbsp;255</code>, then the
+	 * result is equal to: null null null null null null null null null null
+	 * null null null null <blockquote>
+	 * 
+	 * <pre>
+	 * (char) ((b1 &lt;&lt; 8) | b2)
+	 * </pre>
+	 * 
+	 * </blockquote>
+	 * <p>
+	 * This method blocks until the two bytes are read,
+	 * the end of the stream is detected, or an exception is thrown.
+	 *
+	 * @return the next two bytes of this file, interpreted as a
+	 *         <code>char</code>.
+	 * @exception EOFException if this file reaches the end before reading two
+	 *                bytes.
+	 * @exception IOException if an I/O error occurs.
+	 */
+	public synchronized final char readChar() throws IOException {
+		resetPosition();
+		return raf.readChar();
+	}
+
+	/**
+	 * Reads a signed 32-bit integer from this file. This method reads 4 bytes
+	 * from the file, starting at the current file pointer. If the bytes read,
+	 * in order, are
+	 * <code>b1</code>,
+	 * <code>b2</code>,
+	 * <code>b3</code>, and
+	 * <code>b4</code>, where
+	 * <code>0&nbsp;&lt;=&nbsp;b1, b2, b3, b4&nbsp;&lt;=&nbsp;255</code>, then
+	 * the result is equal to: null null null null null null null null null null
+	 * null null null null <blockquote>
+	 * 
+	 * <pre>
+	 * (b1 &lt;&lt; 24) | (b2 &lt;&lt; 16) + (b3 &lt;&lt; 8) + b4
+	 * </pre>
+	 * 
+	 * </blockquote>
+	 * <p>
+	 * This method blocks until the four bytes are read,
+	 * the end of the stream is detected, or an exception is thrown.
+	 *
+	 * @return the next four bytes of this file, interpreted as an
+	 *         <code>int</code>.
+	 * @exception EOFException if this file reaches the end before reading four
+	 *                bytes.
+	 * @exception IOException if an I/O error occurs.
+	 */
+	public synchronized final int readInt() throws IOException {
+		resetPosition();
+		return raf.readInt();
+	}
+
+	/**
+	 * Reads a signed 64-bit integer from this file. This method reads eight
+	 * bytes from the file, starting at the current file pointer. If the bytes
+	 * read, in order, are
+	 * <code>b1</code>,
+	 * <code>b2</code>,
+	 * <code>b3</code>,
+	 * <code>b4</code>,
+	 * <code>b5</code>,
+	 * <code>b6</code>,
+	 * <code>b7</code>, and
+	 * <code>b8,</code> where: null null null null null null null null null null
+	 * null null null null <blockquote>
+	 * 
+	 * <pre>
+	 *     0 &lt;= b1, b2, b3, b4, b5, b6, b7, b8 &lt;=255,
+	 * </pre>
+	 * 
+	 * </blockquote>
+	 * <p>
+	 * then the result is equal to: null null null null
+	 * null null null null null null null null null null
+	 * <p>
+	 * <blockquote>
+	 * 
+	 * <pre>
+	 * ((long) b1 &lt;&lt; 56) + ((long) b2 &lt;&lt; 48) + ((long) b3 &lt;&lt; 40) + ((long) b4 &lt;&lt; 32) + ((long) b5 &lt;&lt; 24)
+	 * 	+ ((long) b6 &lt;&lt; 16) + ((long) b7 &lt;&lt; 8) + b8
+	 * </pre>
+	 * 
+	 * </blockquote>
+	 * <p>
+	 * This method blocks until the eight bytes are
+	 * read, the end of the stream is detected, or an exception is thrown.
+	 *
+	 * @return the next eight bytes of this file, interpreted as a
+	 *         <code>long</code>.
+	 * @exception EOFException if this file reaches the end before reading eight
+	 *                bytes.
+	 * @exception IOException if an I/O error occurs.
+	 */
+	public synchronized final long readLong() throws IOException {
+		resetPosition();
+		return raf.readLong();
+	}
+
+	/**
+	 * Reads a
+	 * <code>float</code> from this file. This method reads an
+	 * <code>int</code> value, starting at the current file pointer, as if by
+	 * the
+	 * <code>readInt</code> method and then converts that
+	 * <code>int</code> to a
+	 * <code>float</code> using the
+	 * <code>intBitsToFloat</code> method in class
+	 * <code>Float</code>.
+	 * <p>
+	 * This method blocks until the four bytes are read,
+	 * the end of the stream is detected, or an exception is thrown.
+	 *
+	 * @return the next four bytes of this file, interpreted as a
+	 *         <code>float</code>.
+	 * @exception EOFException if this file reaches the end before reading four
+	 *                bytes.
+	 * @exception IOException if an I/O error occurs.
+	 * @see java.io.RandomAccessFile#readInt()
+	 * @see java.lang.Float#intBitsToFloat(int)
+	 */
+	public synchronized final float readFloat() throws IOException {
+		resetPosition();
+		return raf.readFloat();
+	}
+
+	/**
+	 * Reads a
+	 * <code>double</code> from this file. This method reads a
+	 * <code>long</code> value, starting at the current file pointer, as if by
+	 * the
+	 * <code>readLong</code> method and then converts that
+	 * <code>long</code> to a
+	 * <code>double</code> using the
+	 * <code>longBitsToDouble</code> method in class
+	 * <code>Double</code>.
+	 * <p>
+	 * This method blocks until the eight bytes are
+	 * read, the end of the stream is detected, or an exception is thrown.
+	 *
+	 * @return the next eight bytes of this file, interpreted as a
+	 *         <code>double</code>.
+	 * @exception EOFException if this file reaches the end before reading eight
+	 *                bytes.
+	 * @exception IOException if an I/O error occurs.
+	 * @see java.io.RandomAccessFile#readLong()
+	 * @see java.lang.Double#longBitsToDouble(long)
+	 */
+	public synchronized final double readDouble() throws IOException {
+		resetPosition();
+		return raf.readDouble();
+	}
+
+	/**
+	 * <p>
+	 * Read the file line by line omitting the line separator.
+	 * </p>
+	 * <p>
+	 * see
+	 * {@link java.io.RandomAccessFile#readLine() readLine()} and see
+	 * {@link java.io.BufferedReader#readLine(boolean) readLine(boolean ignoreLF)}.
+	 * <p>
+	 *
+	 * <p>
+	 * Subsequent calls of this method are buffered. If certain other
+	 * methods that are affected by the current position of the reader in the
+	 * file is called after this method, the position is set to the start of the
+	 * next line and the buffer is invalidated.
+	 * </p>
+	 *
+	 * <p>
+	 * This method is copied from
+	 * {@link java.io.BufferedReader BufferedReader} with minor changes like
+	 * tracking position (offset) were next line starts.
+	 * </p>
+	 *
+	 * @return the next line of text from this file, or null if end of file is
+	 *         encountered before even one byte is read.
+	 * @exception IOException if an I/O error occurs.
+	 */
+	public synchronized final String readLine(boolean ignoreLF) throws IOException {
+
+		StringBuilder s = null;
+		int startChar;
+		int separatorIndex = 0;
+
+		boolean omitLF = ignoreLF || skipLF;
+
+		bufferLoop: for (;;) {
+
+			if (nextChar >= nChars) {
+				fill();
+			}
+			if (nextChar >= nChars) { /* EOF */
+				if (s != null && s.length() > 0) {
+					// EOF -> hence no need to adjust position in file
+					// changed by fill()
+					return s.toString();
+				} else {
+					return null;
+				}
+			}
+			boolean eol = false;
+			char c = 0;
+			int i;
+
+			/* Skip a leftover '\n', if necessary */
+			if (omitLF && charBuffer[nextChar] == '\n') {
+				nextChar++;
+			}
+			skipLF = false;
+			omitLF = false;
+
+			charLoop: for (i = nextChar; i < nChars; i++) {
+				c = charBuffer[i];
+				if (c == '\n' || c == '\r') {
+					eol = true;
+					break charLoop;
+				}
+			}
+
+			startChar = nextChar;
+			nextChar = i;
+
+			if (eol) {
+				String str;
+				if (s == null) {
+					str = new String(charBuffer, startChar, i - startChar);
+				} else {
+					s.append(charBuffer, startChar, i - startChar);
+					str = s.toString();
+				}
+				nextChar++;
+				if (c == '\r') {
+					skipLF = true;
+					if (nextChar >= nChars) {
+						fill();
+					}
+					if (charBuffer[nextChar] == '\n') {
+						separatorIndex = 1;
+					}
+				}
+				actualFilePointer = lastOffset + nextChar + separatorIndex;
+				return str;
+			}
+
+			if (s == null) {
+				s = new StringBuilder(defaultExpectedLineLength);
+			}
+			s.append(charBuffer, startChar, i - startChar);
+		}
+	}
+
+	/**
+	 * see {@link #readLine(boolean) readLine(boolean ignoreLF)}
+	 *
+	 * @return
+	 * @throws IOException
+	 */
+	public synchronized String readLine() throws IOException {
+		return readLine(false);
+	}
+
+	private void fill() throws IOException {
+
+		lastOffset = raf.getFilePointer();
+		actualFilePointer = lastOffset;
+		final byte[] buffer = new byte[bufferSize];
+		final int n = raf.read(buffer);
+		if (n > 0) {
+			nChars = n;
+			nextChar = 0;
+		}
+		for (int i = 0; i < buffer.length; i++) {
+			charBuffer[i] = (char) buffer[i];
+		}
+	}
+
+	/**
+	 * Reads in a string from this file. The string has been encoded using a <a
+	 * href="DataInput.html#modified-utf-8">modified UTF-8</a> format.
+	 * <p>
+	 * The
+	 * first two bytes are read, starting from the current file pointer, as if
+	 * by
+	 * <code>readUnsignedShort</code>. This value gives the number of following
+	 * bytes that are in the encoded string, not the length of the resulting
+	 * string. The following bytes are then interpreted as bytes encoding
+	 * characters in the modified UTF-8 format and are converted into
+	 * characters.
+	 * <p>
+	 * This method blocks until all the bytes are read, the end
+	 * of the stream is detected, or an exception is thrown.
+	 *
+	 * @return a Unicode string.
+	 * @exception EOFException if this file reaches the end before reading all
+	 *                the bytes.
+	 * @exception IOException if an I/O error occurs.
+	 * @exception UTFDataFormatException if the bytes do not represent valid
+	 *                modified UTF-8 encoding of a Unicode string.
+	 * @see java.io.RandomAccessFile#readUnsignedShort()
+	 */
+	public synchronized final String readUTF() throws IOException {
+		resetPosition();
+		return raf.readUTF();
+	}
+
+	/**
+	 * Writes a
+	 * <code>boolean</code> to the file as a one-byte value. The value
+	 * <code>true</code> is written out as the value
+	 * <code>(byte)1</code>; the value
+	 * <code>false</code> is written out as the value
+	 * <code>(byte)0</code>. The write starts at the current position of the
+	 * file pointer.
+	 *
+	 * @param v a <code>boolean</code> value to be written.
+	 * @exception IOException if an I/O error occurs.
+	 */
+	public synchronized final void writeBoolean(boolean v) throws IOException {
+		resetPosition();
+		raf.writeBoolean(v);
+	}
+
+	/**
+	 * Writes a
+	 * <code>byte</code> to the file as a one-byte value. The write starts at
+	 * the current position of the file pointer.
+	 *
+	 * @param v a <code>byte</code> value to be written.
+	 * @exception IOException if an I/O error occurs.
+	 */
+	public synchronized final void writeByte(int v) throws IOException {
+		resetPosition();
+		raf.writeByte(v);
+	}
+
+	/**
+	 * Writes a
+	 * <code>short</code> to the file as two bytes, high byte first. The write
+	 * starts at the current position of the file pointer.
+	 *
+	 * @param v a <code>short</code> to be written.
+	 * @exception IOException if an I/O error occurs.
+	 */
+	public synchronized final void writeShort(int v) throws IOException {
+		resetPosition();
+		raf.writeShort(v);
+	}
+
+	/**
+	 * Writes a
+	 * <code>char</code> to the file as a two-byte value, high byte first. The
+	 * write starts at the current position of the file pointer.
+	 *
+	 * @param v a <code>char</code> value to be written.
+	 * @exception IOException if an I/O error occurs.
+	 */
+	public synchronized final void writeChar(int v) throws IOException {
+		resetPosition();
+		raf.writeChar(v);
+	}
+
+	/**
+	 * Writes an
+	 * <code>int</code> to the file as four bytes, high byte first. The write
+	 * starts at the current position of the file pointer.
+	 *
+	 * @param v an <code>int</code> to be written.
+	 * @exception IOException if an I/O error occurs.
+	 */
+	public synchronized final void writeInt(int v) throws IOException {
+		resetPosition();
+		raf.writeInt(v);
+	}
+
+	/**
+	 * Writes a
+	 * <code>long</code> to the file as eight bytes, high byte first. The write
+	 * starts at the current position of the file pointer.
+	 *
+	 * @param v a <code>long</code> to be written.
+	 * @exception IOException if an I/O error occurs.
+	 */
+	public synchronized final void writeLong(long v) throws IOException {
+		resetPosition();
+		raf.writeLong(v);
+	}
+
+	/**
+	 * Converts the float argument to an
+	 * <code>int</code> using the
+	 * <code>floatToIntBits</code> method in class
+	 * <code>Float</code>, and then writes that
+	 * <code>int</code> value to the file as a four-byte quantity, high byte
+	 * first. The write starts at the current position of the file pointer.
+	 *
+	 * @param v a <code>float</code> value to be written.
+	 * @exception IOException if an I/O error occurs.
+	 * @see java.lang.Float#floatToIntBits(float)
+	 */
+	public synchronized final void writeFloat(float v) throws IOException {
+		resetPosition();
+		raf.writeFloat(v);
+	}
+
+	/**
+	 * Converts the double argument to a
+	 * <code>long</code> using the
+	 * <code>doubleToLongBits</code> method in class
+	 * <code>Double</code>, and then writes that
+	 * <code>long</code> value to the file as an eight-byte quantity, high byte
+	 * first. The write starts at the current position of the file pointer.
+	 *
+	 * @param v a <code>double</code> value to be written.
+	 * @exception IOException if an I/O error occurs.
+	 * @see java.lang.Double#doubleToLongBits(double)
+	 */
+	public synchronized final void writeDouble(double v) throws IOException {
+		resetPosition();
+		raf.writeDouble(v);
+	}
+
+	/**
+	 * Writes the string to the file as a sequence of bytes. Each character in
+	 * the string is written out, in sequence, by discarding its high eight
+	 * bits. The write starts at the current position of the file pointer.
+	 *
+	 * @param s a string of bytes to be written.
+	 * @exception IOException if an I/O error occurs.
+	 */
+	public synchronized final void writeBytes(String s) throws IOException {
+		resetPosition();
+		raf.writeBytes(s);
+	}
+
+	/**
+	 * Writes a string to the file as a sequence of characters. Each character
+	 * is written to the data output stream as if by the
+	 * <code>writeChar</code> method. The write starts at the current position
+	 * of the file pointer.
+	 *
+	 * @param s a <code>String</code> value to be written.
+	 * @exception IOException if an I/O error occurs.
+	 * @see java.io.RandomAccessFile#writeChar(int)
+	 */
+	public synchronized final void writeChars(String s) throws IOException {
+		resetPosition();
+		raf.writeChars(s);
+	}
+
+	/**
+	 * Writes a string to the file using <a
+	 * href="DataInput.html#modified-utf-8">modified UTF-8</a> encoding in a
+	 * machine-independent manner.
+	 * <p>
+	 * First, two bytes are written to the file,
+	 * starting at the current file pointer, as if by the
+	 * <code>writeShort</code> method giving the number of bytes to follow. This
+	 * value is the number of bytes actually written out, not the length of the
+	 * string. Following the length, each character of the string is output, in
+	 * sequence, using the modified UTF-8 encoding for each character.
+	 *
+	 * @param str a string to be written.
+	 * @exception IOException if an I/O error occurs.
+	 */
+	public synchronized final void writeUTF(String str) throws IOException {
+		resetPosition();
+		raf.writeUTF(str);
+	}
+
+	private void resetPosition() throws IOException {
+		if (actualFilePointer != null) {
+			raf.seek(actualFilePointer);
+			actualFilePointer = null;
+		}
+		nChars = 0;
+		nextChar = 0;
+	}
+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.emf.emfstore.server.model/src/org/eclipse/emf/emfstore/internal/server/model/versioning/impl/persistent/XmlTags.java b/bundles/org.eclipse.emf.emfstore.server.model/src/org/eclipse/emf/emfstore/internal/server/model/versioning/impl/persistent/XmlTags.java
index ee74dfd..f46efbc 100644
--- a/bundles/org.eclipse.emf.emfstore.server.model/src/org/eclipse/emf/emfstore/internal/server/model/versioning/impl/persistent/XmlTags.java
+++ b/bundles/org.eclipse.emf.emfstore.server.model/src/org/eclipse/emf/emfstore/internal/server/model/versioning/impl/persistent/XmlTags.java
@@ -36,8 +36,12 @@
 	 * Change package opening tag.
 	 */
 	public static final String CHANGE_PACKAGE_START = "<org.eclipse.emf.emfstore.internal.server.model.versioning:ChangePackage " //$NON-NLS-1$
-		+ "xmi:version=\"2.0\" xmlns:xmi=\"http://www.omg.org/XMI\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:org.eclipse.emf.emfstore.internal.server.model.versioning=\"http://eclipse.org/emf/emfstore/server/model/versioning\" xmlns:org.eclipse.emf.emfstore.internal.server.model.versioning.operations=\"http://eclipse.org/emf/emfstore/server/model/versioning/operations\">" //$NON-NLS-1$
-		+ NEWLINE;
+		+ "xmi:version=\"2.0\" xmlns:xmi=\"http://www.omg.org/XMI\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:org.eclipse.emf.emfstore.internal.server.model.versioning=\"http://eclipse.org/emf/emfstore/server/model/versioning\" xmlns:org.eclipse.emf.emfstore.internal.server.model.versioning.operations=\"http://eclipse.org/emf/emfstore/server/model/versioning/operations\">"; //$NON-NLS-1$
+
+	/**
+	 * Change package opening tag with newline.
+	 */
+	public static final String CHANGE_PACKAGE_START_WITH_NEWLINE = CHANGE_PACKAGE_START + NEWLINE;
 
 	/**
 	 * Change package closing tag.