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

com.emc.mongoose.api.model.item.BasicDataItem Maven / Gradle / Ivy

There is a newer version: 4.0.0-alpha5
Show newest version
package com.emc.mongoose.api.model.item;

import com.emc.mongoose.api.model.data.DataInput;
import com.emc.mongoose.api.model.data.DataCorruptionException;
import com.emc.mongoose.api.model.data.DataSizeException;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import static com.emc.mongoose.api.model.item.DataItem.getRangeOffset;

import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.BitSet;
import static java.lang.Math.min;

/**
 Created by kurila on 09.05.14.
 A data item which may produce uniformly distributed non-compressible content.
 Uses UniformDataSource as a ring buffer. Not thread safe.
 Note: the {@link java.nio.channels.ReadableByteChannel#read(java.nio.ByteBuffer)}
 method implementation will not return 0 or -1 (endless)
 */
public class BasicDataItem
extends BasicItem
implements DataItem {
	//
	private static final String
		FMT_MSG_OFFSET = "Data item offset is not correct hexadecimal value: \"%s\"",
		FMT_MSG_SIZE = "Data item size is not correct hexadecimal value: \"%s\"",
		FMT_MSG_MASK = "Ranges mask is not correct hexadecimal value: %s",
		STR_EMPTY_MASK = "0";
	//
	private static final char LAYER_MASK_SEP = '/';
	//
	private volatile DataInput dataInput;
	private int dataInputSize;
	//
	protected int layerNum = 0;
	//
	protected long offset = 0;
	protected long position = 0;
	protected long size = 0;
	//
	protected final BitSet modifiedRangesMask = new BitSet(Long.SIZE);
	////////////////////////////////////////////////////////////////////////////////////////////////
	public BasicDataItem() {
		super();
	}
	//
	public BasicDataItem(final String value) {
		this(value, value.indexOf(','));
	}
	//
	private BasicDataItem(final String value, final int firstCommaPos) {

		super(value.substring(0, firstCommaPos));

		int prevCommaPos = firstCommaPos;
		int nextCommaPos = value.indexOf(',', prevCommaPos + 1);
		if(nextCommaPos < prevCommaPos) {
			throw new IllegalArgumentException("Invalid data item description: " + value);
		}
		final String offsetInfo = value.substring(prevCommaPos + 1, nextCommaPos);
		try {
			offset(Long.parseLong(offsetInfo, 0x10));
		} catch(final NumberFormatException e) {
			throw new IllegalArgumentException(String.format(FMT_MSG_OFFSET, offsetInfo));
		}

		prevCommaPos = nextCommaPos;
		nextCommaPos = value.indexOf(',', prevCommaPos + 1);
		if(nextCommaPos < prevCommaPos) {
			throw new IllegalArgumentException("Invalid data item description: " + value);
		}
		final String sizeInfo = value.substring(prevCommaPos + 1, nextCommaPos);
		try {
			truncate(Long.parseLong(sizeInfo, 10));
		} catch(final NumberFormatException e) {
			throw new IllegalArgumentException(String.format(FMT_MSG_SIZE, sizeInfo));
		}

		prevCommaPos = nextCommaPos;
		final String rangesInfo = value.substring(prevCommaPos + 1);
		final int sepPos = rangesInfo.indexOf(LAYER_MASK_SEP, 0);
		try {
			// extract hexadecimal layer number
			layerNum = Integer.parseInt(rangesInfo.substring(0, sepPos), 0x10);
			// extract hexadecimal mask, convert into bit set and add to the existing mask
			final String rangesMask = rangesInfo.substring(sepPos + 1, rangesInfo.length());
			final char rangesMaskChars[];
			if(rangesMask.length() == 0) {
				rangesMaskChars = ("00" + rangesMask).toCharArray();
			} else if(rangesMask.length() % 2 == 1) {
				rangesMaskChars = ("0" + rangesMask).toCharArray();
			} else {
				rangesMaskChars = rangesMask.toCharArray();
			}
			// method "or" to merge w/ the existing mask
			modifiedRangesMask.or(BitSet.valueOf(Hex.decodeHex(rangesMaskChars)));
		} catch(final DecoderException | NumberFormatException e) {
			throw new IllegalArgumentException(String.format(FMT_MSG_MASK, rangesInfo));
		}
	}
	//
	public BasicDataItem(final long offset, final long size) {
		this(Long.toString(offset, Character.MAX_RADIX), offset, size, 0);
	}
	//
	public BasicDataItem(final String name, final long offset, final long size) {
		this(name, offset, size, 0);
	}
	//
	public BasicDataItem(final long offset, final long size, final int layerNum) {
		this();
		this.layerNum = layerNum;
		this.offset = offset;
		this.size = size;
	}
	//
	public BasicDataItem(
		final String name, final long offset, final long size, final int layerNum
	) {
		super(name);
		this.layerNum = layerNum;
		this.offset = offset;
		this.size = size;
	}
	//
	public BasicDataItem(
		final BasicDataItem baseDataItem, final long internalOffset, final long size,
		final boolean nextLayer
	) {
		this.dataInput = baseDataItem.dataInput;
		this.dataInputSize = baseDataItem.dataInputSize;
		this.offset = baseDataItem.offset + internalOffset;
		this.size = size;
		this.layerNum = nextLayer ? baseDataItem.layerNum : baseDataItem.layerNum;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////
	// Human readable "serialization" implementation ///////////////////////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////////////////
	private static final ThreadLocal STRB = new ThreadLocal() {
		@Override
		protected final StringBuilder initialValue() {
			return new StringBuilder();
		}
	};

	@Override
	public String toString() {
		final StringBuilder strb = STRB.get();
		strb.setLength(0); // reset
		return strb
			.append(super.toString()).append(',')
			.append(Long.toString(offset, 0x10)).append(',')
			.append(size).append(',')
			.append(Integer.toHexString(layerNum)).append('/')
			.append(
				modifiedRangesMask.isEmpty() ?
					STR_EMPTY_MASK : Hex.encodeHexString(modifiedRangesMask.toByteArray())
			).toString();
	}

	@Override
	public String toString(final String itemPath) {
		final StringBuilder strBuilder = STRB.get();
		strBuilder.setLength(0); // reset
		return strBuilder
			.append(super.toString(itemPath)).append(',')
			.append(Long.toString(offset, 0x10)).append(',')
			.append(size).append(',')
			.append(Integer.toHexString(layerNum)).append('/')
			.append(
				modifiedRangesMask.isEmpty() ?
					STR_EMPTY_MASK : Hex.encodeHexString(modifiedRangesMask.toByteArray())
			).toString();
	}
	////////////////////////////////////////////////////////////////////////////////////////////////
	@Override
	public final DataInput getDataInput() {
		return dataInput;
	}
	//
	@Override
	public final void setDataInput(final DataInput dataInput) {
		this.dataInput = dataInput;
		this.dataInputSize = dataInput.getSize();
	}
	//
	@Override
	public void reset() {
		super.reset();
		position = 0;
	}
	//
	@Override
	public final int layer() {
		return layerNum;
	}
	//
	@Override
	public final void layer(final int layerNum) {
		this.layerNum = layerNum;
	}
	//
	@Override
	public final void size(final long size) {
		this.size = size;
	}
	//
	@Override
	public final long offset() {
		return offset;
	}
	//
	@Override
	public final void offset(final long offset) {
		this.offset = offset < 0 ? Long.MAX_VALUE + offset + 1 : offset;
		position = 0;
	}
	//
	@Override
	public BasicDataItem slice(final long from, final long partSize) {
		if(from < 0) {
			throw new IllegalArgumentException();
		}
		if(partSize < 1) {
			throw new IllegalArgumentException();
		}
		final BasicDataItem dataItemSlice = new BasicDataItem(
			name, offset + from, partSize, layerNum
		);
		if(dataInput != null) {
			dataItemSlice.setDataInput(dataInput);
		}
		return dataItemSlice;
	}
	//
	public long position() {
		return position;
	}
	//
	@Override
	public final BasicDataItem position(final long position) {
		this.position = position;
		return this;
	}
	//
	@Override
	public long size() {
		return size;
	}
	//
	@Override
	public BasicDataItem truncate(final long size) {
		this.size = size;
		return this;
	}
	//
	@Override
	public final long getRangeSize(final int i) {
		return min(getRangeOffset(i + 1), size) - getRangeOffset(i);
	}

	////////////////////////////////////////////////////////////////////////////////////////////////
	// UPDATE //////////////////////////////////////////////////////////////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////////////////
	@Override
	public final boolean isUpdated() {
		return layerNum > 0 || !modifiedRangesMask.isEmpty();
	}
	
	@Override
	public final void commitUpdatedRanges(final BitSet[] updatingRangesMaskPair) {
		if(updatingRangesMaskPair[1].isEmpty()) {
			modifiedRangesMask.or(updatingRangesMaskPair[0]);
		} else {
			modifiedRangesMask.clear();
			modifiedRangesMask.or(updatingRangesMaskPair[1]);
			layerNum ++;
		}
	}
	
	@Override
	public final boolean isRangeUpdated(final int rangeIdx) {
		return modifiedRangesMask.get(rangeIdx);
	}
	
	@Override
	public final int getUpdatedRangesCount() {
		return modifiedRangesMask.cardinality();
	}
	////////////////////////////////////////////////////////////////////////////////////////////////
	// ByteChannels implementation
	////////////////////////////////////////////////////////////////////////////////////////////////
	@Override
	public final void close() {
	}
	//
	@Override
	public final boolean isOpen() {
		return true;
	}
	//
	@Override
	public final int read(final ByteBuffer dst) {
		final int n;
		final MappedByteBuffer ringBuff = (MappedByteBuffer) dataInput
			.getLayer(layerNum)
			.asReadOnlyBuffer();
		ringBuff.position((int) ((offset + position) % dataInputSize));
		// bytes count to transfer
		n = Math.min(dst.remaining(), ringBuff.remaining());
		ringBuff.limit(ringBuff.position() + n);
		// do the transfer
		dst.put(ringBuff);
		position += n;
		return n;
	}
	//
	@Override
	public final int write(final ByteBuffer src)
	throws DataCorruptionException, DataSizeException {
		if(src == null) {
			return 0;
		}
		int m;
		final MappedByteBuffer ringBuff = (MappedByteBuffer) dataInput
			.getLayer(layerNum)
			.asReadOnlyBuffer();
		ringBuff.position((int) ((offset + position) % dataInputSize));
		final int n = Math.min(src.remaining(), ringBuff.remaining());
		if(n > 0) {
			byte bs, bi;
			for(m = 0; m < n; m ++) {
				bs = ringBuff.get();
				bi = src.get();
				if(bs != bi) {
					throw new DataCorruptionException(m, bs, bi);
				}
			}
			position += n;
		} else {
			return n;
		}
		return m;
	}

	@Override
	public final int write(final WritableByteChannel chanDst, final long maxCount)
	throws IOException {
		final MappedByteBuffer ringBuff = (MappedByteBuffer) dataInput
			.getLayer(layerNum)
			.asReadOnlyBuffer();
		ringBuff.position((int) ((offset + position) % dataInputSize));
		int n = (int) Math.min(maxCount, ringBuff.remaining());
		ringBuff.limit(ringBuff.position() + n);
		n = chanDst.write(ringBuff);
		position += n;
		return n;
	}
	
	@Override
	public final int readAndVerify(final ReadableByteChannel chanSrc, final MappedByteBuffer buff)
	throws DataCorruptionException, IOException {
		int n;
		final MappedByteBuffer ringBuff = (MappedByteBuffer) dataInput
			.getLayer(layerNum)
			.asReadOnlyBuffer();
		ringBuff.position((int) ((offset + position) % dataInputSize));
		n = ringBuff.remaining();
		if(buff.limit() > n) {
			buff.limit(n);
		}

		n = chanSrc.read(buff);
		position += n;

		if(n > 0) {
			buff.flip();
			
			final int wordCount = n >>> 3;
			if(wordCount > 0) {
				long ws, wi;
				for(int k = 0; k < wordCount; k ++) {
					ws = ringBuff.getLong();
					wi = buff.getLong();
					if(ws != wi) {
						final int wordPos = k << 3;
						byte bs, bi;
						for(int i = 0; i < 8; i ++) {
							bs = (byte) ws;
							ws >>= 8;
							bi = (byte) wi;
							wi >>= 8;
							if(bs != bi) {
								throw new DataCorruptionException(wordPos + i, bs, bi);
							}
						}
					}
				}
			}

			final int tailByteCount = n & 7;
			if(tailByteCount > 0) {
				byte bs, bi;
				for(int m = 0; m < tailByteCount; m ++) {
					bs = ringBuff.get();
					bi = buff.get();
					if(bs != bi) {
						throw new DataCorruptionException(m, bs, bi);
					}
				}
			}
		}
		return n;
	}

	@Override
	public boolean equals(final Object o) {
		if(o == this) {
			return true;
		}
		if(!(o instanceof BasicDataItem)) {
			return false;
		}
		final BasicDataItem other = (BasicDataItem) o;
		return super.equals(other) && offset == other.offset;
	}
	//
	@Override
	public int hashCode() {
		return super.hashCode() ^ (int) offset;
	}
	
	@Override
	public void writeExternal(final ObjectOutput out)
	throws IOException {
		super.writeExternal(out);
		out.writeInt(layerNum);
		out.writeLong(offset);
		out.writeLong(position);
		out.writeLong(size);
		final byte buff[] = modifiedRangesMask.toByteArray();
		out.writeInt(buff.length);
		out.write(buff);
	}
	
	@Override
	public void readExternal(final ObjectInput in)
	throws IOException, ClassNotFoundException {
		super.readExternal(in);
		layerNum = in.readInt();
		offset = in.readLong();
		position = in.readLong();
		size = in.readLong();
		final int len = in.readInt();
		final byte buff[] = new byte[len];
		in.readFully(buff);
		modifiedRangesMask.or(BitSet.valueOf(buff));
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy