/**
* Copyright (c) 2012, University of Konstanz, Distributed Systems Group
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the University of Konstanz nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.jscsi.parser.datasegment;
import java.nio.ByteBuffer;
import java.util.NoSuchElementException;
import org.jscsi.exception.InternetSCSIException;
import org.jscsi.parser.Constants;
import org.jscsi.utils.ByteBufferCache;
/**
* <h1>DataSegment</h1>
* <p>
* This class represents a basic interface for a data segment contained in a <code>ProtocolDataUnit</code>
* object.
*
* @author Volker Wildi
*/
public abstract class AbstractDataSegment implements IDataSegment {
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Constant, which is used to partioning the <code>dataBuffer</code> of this
* size. Typically, this is equal to the MaxRecvDataSegmentLength.
*/
private final int maxChunkSize;
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* The buffer of this data segment.
*/
protected ByteBuffer dataBuffer;
/** The number of bytes actually used by the <code>dataBuffer</code>. */
protected int length;
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Constructor to create a new, empty <code>DataSegment</code> object with
* the given chunk size.
*
* @param initMaxChunkSize
* The maximum size (in bytes) of one chunk, which represents the
* <code>MaxRecvDataSegmentLength</code>.
*/
public AbstractDataSegment(final int initMaxChunkSize) {
maxChunkSize = initMaxChunkSize;
length = 0;
dataBuffer = ByteBufferCache.allocate(length);
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/** {@inheritDoc} */
public int serialize(final ByteBuffer dst, final int off) {
if (dst == null) {
throw new NullPointerException();
}
dst.position(off);
if (dst.remaining() % Constants.BYTES_PER_INT != 0) {
throw new IllegalArgumentException("The buffer length must be a multiple of "
+ Constants.BYTES_PER_INT + ".");
}
dataBuffer.rewind();
if (dst.remaining() < dataBuffer.limit()) {
throw new IllegalArgumentException("The data buffer is too small.");
}
dst.put(dataBuffer);
return dataBuffer.limit();
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/** {@inheritDoc} */
public SettingsMap getSettings() throws InternetSCSIException {
throw new UnsupportedOperationException();
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Returns the total length (with padding), which is needed for the given
* length <code>len</code>.
*
* @param len
* The length of the <code>DataSegment</code>, which want to be
* stored.
* @return The total length.
*/
public static final int getTotalLength(final int len) {
return len + calcPadding(len);
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* This method resizes the data buffer, if necessary. A resizing is not
* needed, when the <code>neededLength</code> is greater than the capacity
* of allocated data buffer. The flag <code>copyData</code> indicates, if
* the old buffer has to be copied into the new bigger data buffer.
*
* @param additionalLength
* The length, which is now needed to store all informations in
* the data buffer.
* @param copyData
* <code>true</code>, if old buffer has to be copied into the new
* buffer. <code>false</code> indicates, that the new buffer is
* initialized with zeros.
*/
protected void resizeBuffer(final int additionalLength, final boolean copyData) {
if (additionalLength < 0) {
throw new IllegalArgumentException("The length must be greater or equal than 0.");
}
dataBuffer.position(length);
// reallocate a bigger dataBuffer, if needed
if (length + additionalLength > dataBuffer.capacity()) {
final ByteBuffer newBuffer = ByteBufferCache.allocate(getTotalLength(length + additionalLength));
// copy old data...
if (copyData) {
dataBuffer.flip();
newBuffer.put(dataBuffer);
}
ByteBufferCache.release(dataBuffer);
dataBuffer = newBuffer;
dataBuffer.limit(getTotalLength(length + additionalLength));
}
length += additionalLength;
}
/** {@inheritDoc} */
public void clear() {
dataBuffer.clear().flip();
length = 0;
}
/** {@inheritDoc} */
public final int getLength() {
return length;
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Compares this object with another object for equality.
*
* @param anObject
* Another object.
* @return <code>true</code>, if the <code>anObject</code> and this object
* are equal. Else <code>false</code> .
*/
public boolean equals(final Object anObject) {
// check for alias
if (this == anObject) {
return true;
}
if (anObject instanceof AbstractDataSegment) {
final AbstractDataSegment anotherDS = (AbstractDataSegment)anObject;
if (length != anotherDS.length) {
return false;
}
dataBuffer.rewind();
anotherDS.dataBuffer.rewind();
return dataBuffer.equals(anotherDS.dataBuffer);
}
return false;
}
/** {@inheritDoc} */
@Override
public int hashCode() {
return super.hashCode();
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/** {@inheritDoc} */
public final int setDataBuffer(final ByteBuffer src, final int off, final int len) {
resizeBuffer(len, false);
src.position(off);
int n = 0;
while (src.hasRemaining() && n++ < length) {
dataBuffer.put(src.get());
}
// OODRIVE
// Fill the padding bytes with 0, the limit is already set to getTotalLength(length) by resizeBuffer
while (n++ < getTotalLength(length)) {
dataBuffer.put((byte) 0);
}
dataBuffer.rewind();
return n;
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/** {@inheritDoc} */
public final String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("Length: ");
sb.append(length);
sb.append(", MaxChunkSize: ");
sb.append(maxChunkSize);
sb.append(", dataBuffer: ");
sb.append(dataBuffer);
return sb.toString();
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/** {@inheritDoc} */
public final DataSegmentIterator iterator() {
return new DataSegmentIterator();
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* This methods calculates the number of padding bytes of the given length.
* This number of the padding bytes is in the range of <code>0, ..., BYTES_PER_INT</code>.
*
* @param len
* The length for which the padding bytes are calculated.
* @return The number of padding bytes.
*/
protected static final int calcPadding(final int len) {
if (len < 0) {
throw new IllegalArgumentException("Length must be a positive number");
}
final int rest = len % Constants.BYTES_PER_INT;
if (rest > 0) {
return Constants.BYTES_PER_INT - rest;
} else {
return 0;
}
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Iterator through the chunks of a <code>DataSegment</code>.
* <p/>
* <b>This class is not resistant against changes to the <code>dataBuffer</code> .</b>
*/
private final class DataSegmentIterator implements IDataSegmentIterator {
/** The actual position of the iterator. */
private int cursor;
/** The data of a chunk. */
private final ByteBuffer data;
/**
* Constructor to create an iterator through the chunks.
*/
private DataSegmentIterator() {
cursor = dataBuffer.position();
data = ByteBufferCache.allocate(maxChunkSize);
}
/** {@inheritDoc} */
public final boolean hasNext() {
return cursor < dataBuffer.limit();
}
/** {@inheritDoc} */
public final IDataSegmentChunk next(final int chunkSize) {
if (!hasNext()) {
throw new NoSuchElementException();
}
if (chunkSize < 0 && chunkSize > maxChunkSize) {
throw new IllegalArgumentException();
}
final int pos = dataBuffer.position();
dataBuffer.position(cursor);
// OODRIVE
//data.position(0).limit(chunkSize);
// Set the limit to the same as dataBuffer to copy all the bytes (even the padding)
if (dataBuffer.limit() <= data.capacity()) {
data.position(0).limit(dataBuffer.limit());
}
else {
data.position(0).limit(data.capacity());
}
while (data.hasRemaining() && dataBuffer.hasRemaining()) {
data.put(dataBuffer.get());
}
final int dataSegmentLength = data.position();
final int totalLength = dataSegmentLength + calcPadding(dataSegmentLength);
// set limit to the next nearest bound (with padding)
data.limit(totalLength);
data.rewind();
dataBuffer.position(pos);
// cursor += chunkSize;
cursor += totalLength;
return new DataSegmentChunk(dataSegmentLength, totalLength);
}
// ----------------------------------------------------------------------
// ----
// ----------------------------------------------------------------------
// ----
// ----------------------------------------------------------------------
// ----
// ----------------------------------------------------------------------
// ----
/**
* <h1>DataSegmentChunk</h1>
* <p/>
* This is a chunk of a data segment.
*
* @author Volker Wildi
*/
private final class DataSegmentChunk implements IDataSegmentChunk {
/** The real length (in bytes) of this chunk (excluding padding). */
private final int usedLength;
/**
* The length (in bytes), which is needed by this chunk (including
* padding).
*/
private final int totalLength;
/**
* Constructor to create a new, empty <code>DataSegmentChunk</code> instance with a given length
* and a total length of this data
* segment chunk.
*
* @param initLength
* The used length (in bytes) of this new <code>DataSegmentChunk</code> instance.
* @param initTotalLength
* The total length (in bytes) of this new <code>DataSegmentChunk</code> instance.
*/
private DataSegmentChunk(final int initLength, final int initTotalLength) {
usedLength = initLength;
totalLength = initTotalLength;
}
/** {@inheritDoc} */
public ByteBuffer getData() {
return data;
}
/** {@inheritDoc} */
public int getLength() {
return usedLength;
}
/** {@inheritDoc} */
public int getTotalLength() {
return totalLength;
}
}
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
}