/*******************************************************************************
* Copyright (c) 2012, 2014 Ericsson, École Polytechnique de Montréal
* Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
*
* 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:
* Alexandre Montplaisir - Initial API and implementation
* Florian Wininger - Allow to change the size of a interval
*******************************************************************************/
package fr.inria.linuxtools.internal.statesystem.core.backend.historytree;
import java.io.IOException;
import java.nio.ByteBuffer;
import fr.inria.linuxtools.statesystem.core.exceptions.StateValueTypeException;
import fr.inria.linuxtools.statesystem.core.exceptions.TimeRangeException;
import fr.inria.linuxtools.statesystem.core.interval.ITmfStateInterval;
import fr.inria.linuxtools.statesystem.core.statevalue.ITmfStateValue;
import fr.inria.linuxtools.statesystem.core.statevalue.TmfStateValue;
/**
* The interval component, which will be contained in a node of the History
* Tree.
*
* @author Alexandre Montplaisir
*/
public final class HTInterval implements ITmfStateInterval, Comparable<HTInterval> {
private static final String errMsg = "Invalid interval data. Maybe your file is corrupt?"; //$NON-NLS-1$
/**
* Size of an entry in the data section.
*
* <pre>
* 16 2 x Timevalue/long (interval start + end)
* + 4 int (key)
* + 1 byte (type)
* + 4 int (valueOffset)
* </pre>
*/
private static final int DATA_ENTRY_SIZE = 25;
/* 'Byte' equivalent for state values types */
private static final byte TYPE_NULL = -1;
private static final byte TYPE_INTEGER = 0;
private static final byte TYPE_STRING = 1;
private static final byte TYPE_LONG = 2;
private static final byte TYPE_DOUBLE = 3;
/* String entry sizes of different state values */
private static final int NO_ENTRY_SIZE = 0;
private static final int LONG_ENTRY_SIZE = 8;
private static final int DOUBLE_ENTRY_SIZE = 8;
// sizes of string values depend on the string itself
private final long start;
private final long end;
private final int attribute;
private final TmfStateValue sv;
/*
* Size of the strings section entry used by this interval (= 0 if not used)
*/
private final int stringsEntrySize;
/**
* Standard constructor
*
* @param intervalStart
* Start time of the interval
* @param intervalEnd
* End time of the interval
* @param attribute
* Attribute (quark) to which the state represented by this
* interval belongs
* @param value
* State value represented by this interval
* @throws TimeRangeException
* If the start time or end time are invalid
*/
public HTInterval(long intervalStart, long intervalEnd, int attribute,
TmfStateValue value) throws TimeRangeException {
if (intervalStart > intervalEnd) {
throw new TimeRangeException();
}
this.start = intervalStart;
this.end = intervalEnd;
this.attribute = attribute;
this.sv = value;
this.stringsEntrySize = computeStringsEntrySize();
}
/**
* "Faster" constructor for inner use only. When we build an interval when
* reading it from disk (with {@link #readFrom}), we already know the size
* of the strings entry, so there is no need to call
* {@link #computeStringsEntrySize()} and do an extra copy.
*/
private HTInterval(long intervalStart, long intervalEnd, int attribute,
TmfStateValue value, int size) throws TimeRangeException {
if (intervalStart > intervalEnd) {
throw new TimeRangeException();
}
this.start = intervalStart;
this.end = intervalEnd;
this.attribute = attribute;
this.sv = value;
this.stringsEntrySize = size;
}
/**
* Reader factory method. Builds the interval using an already-allocated
* ByteBuffer, which normally comes from a NIO FileChannel.
*
* @param buffer
* The ByteBuffer from which to read the information
* @return The interval object
* @throws IOException
* If there was an error reading from the buffer
*/
public static final HTInterval readFrom(ByteBuffer buffer) throws IOException {
HTInterval interval;
long intervalStart, intervalEnd;
int attribute;
TmfStateValue value;
int valueOrOffset, valueSize, res;
byte valueType;
byte array[];
/* Read the Data Section entry */
intervalStart = buffer.getLong();
intervalEnd = buffer.getLong();
attribute = buffer.getInt();
/* Read the 'type' of the value, then react accordingly */
valueType = buffer.get();
valueOrOffset = buffer.getInt();
switch (valueType) {
case TYPE_NULL:
value = TmfStateValue.nullValue();
valueSize = NO_ENTRY_SIZE;
break;
case TYPE_INTEGER:
/* "ValueOrOffset" is the straight value */
value = TmfStateValue.newValueInt(valueOrOffset);
valueSize = NO_ENTRY_SIZE;
break;
case TYPE_STRING:
/* Go read the matching entry in the Strings section of the block */
buffer.mark();
buffer.position(valueOrOffset);
/* the first byte = the size to read */
valueSize = buffer.get();
/*
* Careful though, 'valueSize' is the total size of the entry,
* including the 'size' byte at the start and end (0'ed) byte at the
* end. Here we want 'array' to only contain the real payload of the
* value.
*/
array = new byte[valueSize - 2];
buffer.get(array);
value = TmfStateValue.newValueString(new String(array));
/* Confirm the 0'ed byte at the end */
res = buffer.get();
if (res != 0) {
throw new IOException(errMsg);
}
/*
* Restore the file pointer's position (so we can read the next
* interval)
*/
buffer.reset();
break;
case TYPE_LONG:
/* Go read the matching entry in the Strings section of the block */
buffer.mark();
buffer.position(valueOrOffset);
value = TmfStateValue.newValueLong(buffer.getLong());
valueSize = LONG_ENTRY_SIZE;
/*
* Restore the file pointer's position (so we can read the next
* interval)
*/
buffer.reset();
break;
case TYPE_DOUBLE:
/* Go read the matching entry in the Strings section of the block */
buffer.mark();
buffer.position(valueOrOffset);
value = TmfStateValue.newValueDouble(buffer.getDouble());
valueSize = DOUBLE_ENTRY_SIZE;
/*
* Restore the file pointer's position (so we can read the next
* interval)
*/
buffer.reset();
break;
default:
/* Unknown data, better to not make anything up... */
throw new IOException(errMsg);
}
try {
interval = new HTInterval(intervalStart, intervalEnd, attribute, value, valueSize);
} catch (TimeRangeException e) {
throw new IOException(errMsg);
}
return interval;
}
/**
* Antagonist of the previous constructor, write the Data entry
* corresponding to this interval in a ByteBuffer (mapped to a block in the
* history-file, hopefully)
*
* @param buffer
* The already-allocated ByteBuffer corresponding to a SHT Node
* @param endPosOfStringEntry
* The initial (before calling this function for this interval)
* position of the Strings Entry for this node. This will change
* from one call to the other if we're writing String
* StateValues.
* @return The size of the Strings Entry that was written, if any.
*/
public int writeInterval(ByteBuffer buffer, int endPosOfStringEntry) {
buffer.putLong(start);
buffer.putLong(end);
buffer.putInt(attribute);
buffer.put(getByteFromType(sv.getType()));
switch (getByteFromType(sv.getType())) {
case TYPE_NULL:
case TYPE_INTEGER:
/* We write the 'valueOffset' field as a straight value. */
try {
buffer.putInt(sv.unboxInt());
} catch (StateValueTypeException e) {
/*
* This should not happen, since the value told us it was of
* type Null or Integer (corrupted value?)
*/
e.printStackTrace();
}
break;
case TYPE_STRING:
byte[] byteArrayToWrite;
try {
byteArrayToWrite = sv.unboxStr().getBytes();
} catch (StateValueTypeException e1) {
/* Should not happen, we're in a switch/case for string type */
throw new RuntimeException();
}
/* we use the valueOffset as an offset. */
buffer.putInt(endPosOfStringEntry - stringsEntrySize);
buffer.mark();
buffer.position(endPosOfStringEntry - stringsEntrySize);
/*
* write the Strings entry (1st byte = size, then the bytes, then the 0)
*/
buffer.put((byte) stringsEntrySize);
buffer.put(byteArrayToWrite);
buffer.put((byte) 0);
assert (buffer.position() == endPosOfStringEntry);
buffer.reset();
break;
case TYPE_LONG:
/* we use the valueOffset as an offset. */
buffer.putInt(endPosOfStringEntry - stringsEntrySize);
buffer.mark();
buffer.position(endPosOfStringEntry - stringsEntrySize);
/*
* write the Long in the Strings section
*/
try {
buffer.putLong(sv.unboxLong());
} catch (StateValueTypeException e) {
/*
* This should not happen, since the value told us it was of
* type Long (corrupted value?)
*/
e.printStackTrace();
}
assert (buffer.position() == endPosOfStringEntry);
buffer.reset();
break;
case TYPE_DOUBLE:
/* we use the valueOffset as an offset. */
buffer.putInt(endPosOfStringEntry - stringsEntrySize);
buffer.mark();
buffer.position(endPosOfStringEntry - stringsEntrySize);
/* Write the Double in the Strings section */
try {
buffer.putDouble(sv.unboxDouble());
} catch (StateValueTypeException e) {
/*
* This should not happen, since the value told us it was of
* type Double (corrupted value?)
*/
e.printStackTrace();
}
if (buffer.position() != endPosOfStringEntry) {
throw new IllegalStateException();
}
buffer.reset();
break;
default:
break;
}
return stringsEntrySize;
}
@Override
public long getStartTime() {
return start;
}
@Override
public long getEndTime() {
return end;
}
@Override
public long getViewerEndTime() {
return end + 1;
}
@Override
public int getAttribute() {
return attribute;
}
@Override
public ITmfStateValue getStateValue() {
return sv;
}
@Override
public boolean intersects(long timestamp) {
if (start <= timestamp) {
if (end >= timestamp) {
return true;
}
}
return false;
}
int getStringsEntrySize() {
return stringsEntrySize;
}
/**
* Total serialized size of this interval
*
* @return The interval size
*/
public int getIntervalSize() {
return stringsEntrySize + DATA_ENTRY_SIZE;
}
private int computeStringsEntrySize() {
switch(sv.getType()) {
case NULL:
case INTEGER:
/* Those don't use the strings section at all */
return NO_ENTRY_SIZE;
case LONG:
/* The value's bytes are written directly into the strings section */
return LONG_ENTRY_SIZE;
case DOUBLE:
/* The value is also written directly into the strings section */
return DOUBLE_ENTRY_SIZE;
case STRING:
try {
/* String's length + 2 (1 byte for size, 1 byte for \0 at the end */
return sv.unboxStr().getBytes().length + 2;
} catch (StateValueTypeException e) {
/* We're inside a switch/case for the string type, can't happen */
throw new IllegalStateException(e);
}
default:
/* It's very important that we know how to write the state value in
* the file!! */
throw new IllegalStateException();
}
}
/**
* Compare the END TIMES of different intervals. This is used to sort the
* intervals when we close down a node.
*/
@Override
public int compareTo(HTInterval other) {
if (this.end < other.end) {
return -1;
} else if (this.end > other.end) {
return 1;
} else {
return 0;
}
}
@Override
public boolean equals(Object other) {
if (other instanceof HTInterval &&
this.compareTo((HTInterval) other) == 0) {
return true;
}
return false;
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public String toString() {
/* Only for debug, should not be externalized */
StringBuilder sb = new StringBuilder();
sb.append('[');
sb.append(start);
sb.append(", "); //$NON-NLS-1$
sb.append(end);
sb.append(']');
sb.append(", attribute = "); //$NON-NLS-1$
sb.append(attribute);
sb.append(", value = "); //$NON-NLS-1$
sb.append(sv.toString());
return sb.toString();
}
/**
* Here we determine how state values "types" are written in the 8-bit
* field that indicates the value type in the file.
*/
private static byte getByteFromType(ITmfStateValue.Type type) {
switch(type) {
case NULL:
return TYPE_NULL;
case INTEGER:
return TYPE_INTEGER;
case STRING:
return TYPE_STRING;
case LONG:
return TYPE_LONG;
case DOUBLE:
return TYPE_DOUBLE;
default:
/* Should not happen if the switch is fully covered */
throw new IllegalStateException();
}
}
}