All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.liferay.sync.service.io.delta.Differ Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 */

package com.liferay.sync.service.io.delta;

import gnu.trove.map.hash.TIntIntHashMap;
import gnu.trove.map.hash.TIntObjectHashMap;

import java.io.IOException;

import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;

import java.security.MessageDigest;

/**
 * @author Connor McKay
 */
public class Differ {

	public void delta(
			ReadableByteChannel modifiedReadableByteChannel,
			ByteChannelReader checksumsByteChannelReader,
			ByteChannelWriter deltaByteChannelWriter)
		throws IOException {

		_modifiedReadableByteChannel = modifiedReadableByteChannel;
		_checksumsByteChannelReader = checksumsByteChannelReader;
		_deltaByteChannelWriter = deltaByteChannelWriter;

		_checksumsByteChannelReader.resizeBuffer(DeltaUtil.BUFFER_FACTOR * 20);

		_checksumsByteBuffer = _checksumsByteChannelReader.getBuffer();

		readChecksumsHeader();
		readChecksums();

		_rollingChecksum = new RollingChecksum(
			_modifiedReadableByteChannel, _blockLength);

		_deltaByteChannelWriter.resizeBuffer(
			(_blockLength * DeltaUtil.BUFFER_FACTOR) + 5);

		_deltaByteBuffer = _deltaByteChannelWriter.getBuffer();

		if ((_dataByteBuffer == null) ||
			(_dataByteBuffer.capacity() <
				(_blockLength * DeltaUtil.BUFFER_FACTOR))) {

			_dataByteBuffer = ByteBuffer.allocate(
				_blockLength * DeltaUtil.BUFFER_FACTOR);
		}

		writeDeltaHeader();
		writeDeltaBlocks();
	}

	protected void readChecksums() throws IOException {
		if (_checksumsByteChannelReader.isSeekable()) {
			_weakChecksums = new TIntIntHashMap(_blocksCount, 0.5F, -1, -1);
		}
		else {
			_blockBytesMap = new TIntObjectHashMap<>(_blocksCount);
		}

		for (int blockNumber = 0; blockNumber < _blocksCount; blockNumber++) {
			_checksumsByteChannelReader.ensureData(20);

			int weakChecksum = _checksumsByteBuffer.getInt();

			if (_checksumsByteChannelReader.isSeekable()) {

				// If checksumsByteChannelReader is an instance of
				// SeekableByteChannel, we can significantly reduce heap memory
				// usage and maintain performance by reading the strong
				// checksums as needed

				_checksumsByteChannelReader.skip(16);

				_weakChecksums.put(weakChecksum, blockNumber);
			}
			else {
				byte[] strongChecksum = new byte[16];

				_checksumsByteBuffer.get(strongChecksum);

				// It is possible that there are two or more blocks with the
				// same weak checksum, in which case the map will only contain
				// the strong checksum of the last one. In some cases, this may
				// cause a data block to be sent when a reference block could
				// have been sent, but it doesn't really matter.

				ByteBuffer byteBuffer = ByteBuffer.allocate(20);

				byteBuffer.put(strongChecksum);
				byteBuffer.putInt(blockNumber);

				_blockBytesMap.put(weakChecksum, byteBuffer.array());
			}
		}
	}

	protected void readChecksumsHeader() throws IOException {
		_checksumsByteChannelReader.ensureData(9);

		if (DeltaUtil.PROTOCOL_VERSION != _checksumsByteBuffer.get()) {
			throw new IOException("Unknown protocol version");
		}

		_blockLength = _checksumsByteBuffer.getInt();
		_blocksCount = _checksumsByteBuffer.getInt();
	}

	protected void writeDataBlock() throws IOException {
		if (_dataByteBuffer.position() == 0) {

			// There's nothing in the data buffer

			return;
		}

		_deltaByteChannelWriter.ensureSpace(_dataByteBuffer.position() + 5);

		_deltaByteBuffer.put(DeltaUtil.DATA_KEY);
		_deltaByteBuffer.putInt(_dataByteBuffer.position());

		_dataByteBuffer.flip();

		_deltaByteBuffer.put(_dataByteBuffer);

		_dataByteBuffer.clear();
	}

	protected void writeDeltaBlocks() throws IOException {
		_firstBlockNumber = -1;
		_lastBlockNumber = -1;

		while (_rollingChecksum.hasNext()) {
			int blockNumber = 0;
			byte[] strongChecksum = null;

			if (_checksumsByteChannelReader.isSeekable()) {
				blockNumber = _weakChecksums.get(
					_rollingChecksum.weakChecksum());

				if (blockNumber != -1) {
					int position = 13 + (blockNumber * 20);

					_checksumsByteChannelReader.position(position);

					_checksumsByteChannelReader.ensureData(16);

					strongChecksum = new byte[16];

					_checksumsByteBuffer.get(strongChecksum);
				}
			}
			else {
				byte[] blockBytes = _blockBytesMap.get(
					_rollingChecksum.weakChecksum());

				if (blockBytes != null) {
					ByteBuffer byteBuffer = ByteBuffer.wrap(blockBytes);

					strongChecksum = new byte[16];

					byteBuffer.get(strongChecksum, 0, 16);

					blockNumber = byteBuffer.getInt();
				}
			}

			if ((strongChecksum != null) &&
				MessageDigest.isEqual(
					strongChecksum, _rollingChecksum.strongChecksum())) {

				if (_firstBlockNumber == -1) {
					writeDataBlock();

					_firstBlockNumber = blockNumber;
					_lastBlockNumber = blockNumber;
				}
				else if ((_lastBlockNumber + 1) == blockNumber) {

					// The blocks must be sequential in a reference range block

					_lastBlockNumber = blockNumber;
				}
				else {
					writeReferenceBlock();

					_firstBlockNumber = blockNumber;
					_lastBlockNumber = blockNumber;
				}

				_rollingChecksum.nextBlock();
			}
			else {
				writeReferenceBlock();

				if (!_dataByteBuffer.hasRemaining()) {
					writeDataBlock();
				}

				_dataByteBuffer.put(_rollingChecksum.getFirstByte());

				_rollingChecksum.nextByte();
			}
		}

		// Only one of these should ever actually do something, but it's simpler
		// to call them both than choose between them.

		writeReferenceBlock();
		writeDataBlock();

		_deltaByteChannelWriter.ensureSpace(1);

		_deltaByteBuffer.put(DeltaUtil.EOF_KEY);
	}

	protected void writeDeltaHeader() throws IOException {
		_deltaByteChannelWriter.ensureSpace(5);

		_deltaByteBuffer.put(DeltaUtil.PROTOCOL_VERSION);
		_deltaByteBuffer.putInt(_blockLength);
	}

	protected void writeReferenceBlock() throws IOException {
		if (_firstBlockNumber == -1) {
			return;
		}

		if (_lastBlockNumber == _firstBlockNumber) {
			_deltaByteChannelWriter.ensureSpace(5);

			_deltaByteBuffer.put(DeltaUtil.REFERENCE_KEY);
			_deltaByteBuffer.putInt(_firstBlockNumber);
		}
		else {
			_deltaByteChannelWriter.ensureSpace(9);

			_deltaByteBuffer.put(DeltaUtil.REFERENCE_RANGE_KEY);
			_deltaByteBuffer.putInt(_firstBlockNumber);
			_deltaByteBuffer.putInt(_lastBlockNumber);
		}

		_firstBlockNumber = -1;
		_lastBlockNumber = -1;
	}

	private TIntObjectHashMap _blockBytesMap;
	private int _blockLength;
	private int _blocksCount;
	private ByteBuffer _checksumsByteBuffer;
	private ByteChannelReader _checksumsByteChannelReader;
	private ByteBuffer _dataByteBuffer;
	private ByteBuffer _deltaByteBuffer;
	private ByteChannelWriter _deltaByteChannelWriter;
	private int _firstBlockNumber;
	private int _lastBlockNumber;
	private ReadableByteChannel _modifiedReadableByteChannel;
	private RollingChecksum _rollingChecksum;
	private TIntIntHashMap _weakChecksums;

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy