/******************************************************************************* * Copyright (c) 2011-2016 EclipseSource Muenchen GmbH and others. * * 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: * Edgar Mueller - initial API and implementation * Johannes Faltermeier - initial API and implementation ******************************************************************************/ package org.eclipse.emf.emfstore.internal.server.model.versioning.impl.persistent; 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.InputStream; import java.io.InputStreamReader; import java.io.PipedOutputStream; import java.io.RandomAccessFile; import java.nio.channels.Channels; import java.util.ArrayList; import java.util.List; import org.apache.commons.io.input.ReversedLinesFileReader; import org.eclipse.emf.emfstore.internal.common.model.util.ModelUtil; /** * Abstract super class for implementing types which emit operations when given an {@link ReadLineCapable} type. * */ public abstract class AbstractOperationEmitter implements Closeable { private final Direction direction; 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 AbstractOperationEmitter(Direction direction, File file) { this.direction = direction; operationsFile = file; determineOperationOffsets(); currentOpIndex = direction == Direction.Forward ? 0 : backwardsOffsets.size() - 1; initReader(); } /** * @return the direction the {@link Direction} */ protected final Direction getDirection() { return direction; } private void determineOperationOffsets() { try { final RandomAccessFile randomAccessFile = new RandomAccessFile(operationsFile, "r"); //$NON-NLS-1$ final InputStream inputStream = Channels.newInputStream(randomAccessFile.getChannel()); final InputStreamReader inputStreamReader = new InputStreamReader(inputStream); final BufferedReader bufferedReader = new BufferedReader(inputStreamReader); long filePointer = 0; try { String line; while ((line = bufferedReader.readLine()) != null) { filePointer += line.getBytes().length; final long filePointerAfterReadline = randomAccessFile.getFilePointer(); randomAccessFile.seek(filePointer); int byteAfterLine = randomAccessFile.read(); /* * Line is terminated by either: * \r\n * \r * \n */ if (byteAfterLine == '\r') { filePointer += 1; byteAfterLine = randomAccessFile.read(); } if (byteAfterLine == '\n') { filePointer += 1; } randomAccessFile.seek(filePointerAfterReadline); if (line.contains(XmlTags.CHANGE_PACKAGE_START)) { startOffset = filePointer; } else if (line.contains(XmlTags.OPERATIONS_START_TAG)) { forwardOffsets.add(filePointer); } else if (line.contains(XmlTags.OPERATIONS_END_TAG)) { backwardsOffsets.add(filePointer); } } } finally { bufferedReader.close(); randomAccessFile.close(); } } catch (final IOException ex) { ModelUtil.logException(ex); } } private void initReader() { try { if (getDirection() == 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); } } /** * Returns the current offset. * * @return the current offset */ public long getOffset() { if (currentOpIndex < 0) { return startOffset; } return backwardsOffsets.get(currentOpIndex); } /** * Since an XML Resource needs exactly one root object, we have to write a dummy object to the stream. * * @param pos the {@link PipedOutputStream} * @throws IOException in case there is a problem during write */ private static void writeDummyResourceToStream(PipedOutputStream pos) throws IOException { pos.write(XmlTags.XML_RESOURCE_WITH_EOBJECT.getBytes()); } /** * Reads the file in forward direction and writes read lines to the given stream. * * @param pos the output stream */ protected final void readForward(PipedOutputStream pos) { try { boolean operationsFound = false; boolean withinOperationsElement = false; final boolean isForwardDir = getDirection() == 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) { operationsFound = true; pos.write(line.getBytes()); } line = reader.readLine(); } if (line != null) { withinOperationsElement = false; } if (!operationsFound) { writeDummyResourceToStream(pos); } } 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 operationsFound = false; 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) { operationsFound = true; pos.write(line.getBytes()); } line = reader.readLine(); } if (!operationsFound) { writeDummyResourceToStream(pos); } } catch (final IOException ex) { ModelUtil.logException(ex); } finally { try { pos.close(); } catch (final IOException ex) { ModelUtil.logException(ex); } } } /** * Reads the file in backward direction and writes read lines to the given stream. * * @param pos the output strea, */ protected final void readBackward(PipedOutputStream pos) { if (currentOpIndex < 0) { try { writeDummyResourceToStream(pos); 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); } } } private String getClosingTag(boolean isForward) { return isForward ? XmlTags.OPERATIONS_END_TAG : XmlTags.OPERATIONS_START_TAG; } private String getOpeningTag(boolean isForward) { return isForward ? XmlTags.OPERATIONS_START_TAG : XmlTags.OPERATIONS_END_TAG; } /** * Closes the emitter. */ public void close() { try { reader.close(); } catch (final IOException ex) { ModelUtil.logException(ex); } } }