1 /*******************************************************************************
2 * Copyright (c) 2012, 2015 Ericsson, École Polytechnique de Montréal
3 * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
5 * All rights reserved. This program and the accompanying materials are
6 * made available under the terms of the Eclipse Public License v1.0 which
7 * accompanies this distribution, and is available at
8 * http://www.eclipse.org/legal/epl-v10.html
11 * Alexandre Montplaisir - Initial API and implementation
12 * Florian Wininger - Allow to change the size of a interval
13 * Patrick Tasse - Add message to exceptions
14 *******************************************************************************/
16 package org
.eclipse
.tracecompass
.internal
.statesystem
.core
.backend
.historytree
;
18 import java
.io
.IOException
;
19 import java
.nio
.ByteBuffer
;
20 import java
.nio
.charset
.Charset
;
21 import java
.util
.Objects
;
23 import org
.eclipse
.jdt
.annotation
.NonNull
;
24 import org
.eclipse
.tracecompass
.internal
.provisional
.datastore
.core
.serialization
.ISafeByteBufferReader
;
25 import org
.eclipse
.tracecompass
.internal
.provisional
.datastore
.core
.serialization
.ISafeByteBufferWriter
;
26 import org
.eclipse
.tracecompass
.internal
.provisional
.datastore
.core
.serialization
.SafeByteBufferFactory
;
27 import org
.eclipse
.tracecompass
.internal
.provisional
.statesystem
.core
.statevalue
.CustomStateValue
;
28 import org
.eclipse
.tracecompass
.statesystem
.core
.exceptions
.TimeRangeException
;
29 import org
.eclipse
.tracecompass
.statesystem
.core
.interval
.ITmfStateInterval
;
30 import org
.eclipse
.tracecompass
.statesystem
.core
.statevalue
.ITmfStateValue
;
31 import org
.eclipse
.tracecompass
.statesystem
.core
.statevalue
.TmfStateValue
;
34 * The interval component, which will be contained in a node of the History
37 * @author Alexandre Montplaisir
39 public final class HTInterval
implements ITmfStateInterval
{
41 private static final Charset CHARSET
= Charset
.forName("UTF-8"); //$NON-NLS-1$
43 private static final String errMsg
= "Invalid interval data. Maybe your file is corrupt?"; //$NON-NLS-1$
45 /* 'Byte' equivalent for state values types */
46 private static final byte TYPE_NULL
= -1;
47 private static final byte TYPE_INTEGER
= 0;
48 private static final byte TYPE_STRING
= 1;
49 private static final byte TYPE_LONG
= 2;
50 private static final byte TYPE_DOUBLE
= 3;
51 private static final byte TYPE_CUSTOM
= 20;
53 private final long start
;
54 private final long end
;
55 private final int attribute
;
56 private final @NonNull TmfStateValue sv
;
58 /** Number of bytes used by this interval when it is written to disk */
59 private final int fSizeOnDisk
;
62 * Standard constructor
64 * @param intervalStart
65 * Start time of the interval
67 * End time of the interval
69 * Attribute (quark) to which the state represented by this
72 * State value represented by this interval
73 * @throws TimeRangeException
74 * If the start time or end time are invalid
76 public HTInterval(long intervalStart
, long intervalEnd
, int attribute
,
77 @NonNull TmfStateValue value
) throws TimeRangeException
{
78 if (intervalStart
> intervalEnd
) {
79 throw new TimeRangeException("Start:" + intervalStart
+ ", End:" + intervalEnd
); //$NON-NLS-1$ //$NON-NLS-2$
82 this.start
= intervalStart
;
83 this.end
= intervalEnd
;
84 this.attribute
= attribute
;
86 this.fSizeOnDisk
= computeSizeOnDisk(sv
);
90 * Compute how much space (in bytes) an interval will take in its serialized
91 * form on disk. This is dependent on its state value.
93 private static int computeSizeOnDisk(ITmfStateValue sv
) {
95 * Minimum size is 2x long (start and end), 1x int (attribute) and 1x
98 int minSize
= Long
.BYTES
+ Long
.BYTES
+ Integer
.BYTES
+ Byte
.BYTES
;
100 switch (sv
.getType()) {
104 return (minSize
+ Integer
.BYTES
);
106 return (minSize
+ Long
.BYTES
);
108 return (minSize
+ Double
.BYTES
);
110 String str
= sv
.unboxStr();
111 int strLength
= str
.getBytes(CHARSET
).length
;
113 if (strLength
> Short
.MAX_VALUE
) {
114 throw new IllegalArgumentException("String is too long to be stored in state system: " + str
); //$NON-NLS-1$
118 * String's length + 3 (2 bytes for size, 1 byte for \0 at the end)
120 return (minSize
+ strLength
+ 3);
122 /* Length of serialized value (short) + state value */
123 return (minSize
+ Short
.BYTES
+ ((CustomStateValue
) sv
).getSerializedSize());
126 * It's very important that we know how to write the state value in
129 throw new IllegalStateException();
134 * "Faster" constructor for inner use only. When we build an interval when
135 * reading it from disk (with {@link #readFrom}), we already know the size
136 * of the strings entry, so there is no need to call
137 * {@link #computeStringsEntrySize()} and do an extra copy.
139 private HTInterval(long intervalStart
, long intervalEnd
, int attribute
,
140 @NonNull TmfStateValue value
, int size
) throws TimeRangeException
{
141 if (intervalStart
> intervalEnd
) {
142 throw new TimeRangeException("Start:" + intervalStart
+ ", End:" + intervalEnd
); //$NON-NLS-1$ //$NON-NLS-2$
145 this.start
= intervalStart
;
146 this.end
= intervalEnd
;
147 this.attribute
= attribute
;
149 this.fSizeOnDisk
= size
;
153 * Reader factory method. Builds the interval using an already-allocated
154 * ByteBuffer, which normally comes from a NIO FileChannel.
156 * The interval is just a start, end, attribute and value, this is the
157 * layout of the HTInterval on disk
159 * <li>start (8 bytes)</li>
160 * <li>end (8 bytes)</li>
161 * <li>attribute (4 bytes)</li>
162 * <li>sv type (1 byte)</li>
163 * <li>sv ( 0 bytes for null, 4 for int , 8 for long and double, and the
164 * length of the string +2 for strings (it's variable))</li>
168 * The ByteBuffer from which to read the information
169 * @return The interval object
170 * @throws IOException
171 * If there was an error reading from the buffer
173 public static final HTInterval
readFrom(ByteBuffer buffer
) throws IOException
{
176 int posStart
= buffer
.position();
177 /* Read the Data Section entry */
178 long intervalStart
= buffer
.getLong();
179 long intervalEnd
= buffer
.getLong();
180 int attribute
= buffer
.getInt();
182 /* Read the 'type' of the value, then react accordingly */
183 byte valueType
= buffer
.get();
187 value
= TmfStateValue
.nullValue();
191 value
= TmfStateValue
.newValueInt(buffer
.getInt());
195 /* the first short = the size to read */
196 int valueSize
= buffer
.getShort();
198 byte[] array
= new byte[valueSize
];
200 value
= TmfStateValue
.newValueString(new String(array
, CHARSET
));
202 /* Confirm the 0'ed byte at the end */
203 byte res
= buffer
.get();
205 throw new IOException(errMsg
);
211 /* Go read the matching entry in the Strings section of the block */
212 value
= TmfStateValue
.newValueLong(buffer
.getLong());
216 /* Go read the matching entry in the Strings section of the block */
217 value
= TmfStateValue
.newValueDouble(buffer
.getDouble());
221 short valueSize
= buffer
.getShort();
222 ISafeByteBufferReader safeBuffer
= SafeByteBufferFactory
.wrapReader(buffer
, valueSize
);
223 value
= CustomStateValue
.readSerializedValue(safeBuffer
);
227 /* Unknown data, better to not make anything up... */
228 throw new IOException(errMsg
);
232 return new HTInterval(intervalStart
, intervalEnd
, attribute
, value
, buffer
.position() - posStart
);
233 } catch (TimeRangeException e
) {
234 throw new IOException(errMsg
);
239 * Antagonist of the previous constructor, write the Data entry
240 * corresponding to this interval in a ByteBuffer (mapped to a block in the
241 * history-file, hopefully)
243 * The interval is just a start, end, attribute and value, this is the
244 * layout of the HTInterval on disk
246 * <li>start (8 bytes)</li>
247 * <li>end (8 bytes)</li>
248 * <li>attribute (4 bytes)</li>
249 * <li>sv type (1 byte)</li>
250 * <li>sv ( 0 bytes for null, 4 for int , 8 for long and double, and the
251 * length of the string +2 for strings (it's variable))</li>
255 * The already-allocated ByteBuffer corresponding to a SHT Node
257 public void writeInterval(ByteBuffer buffer
) {
258 final byte byteFromType
= getByteFromType(sv
.getType());
260 buffer
.putLong(start
);
262 buffer
.putInt(attribute
);
263 buffer
.put(byteFromType
);
265 switch (byteFromType
) {
269 buffer
.putInt(sv
.unboxInt());
273 String string
= sv
.unboxStr();
274 byte[] strArray
= string
.getBytes(CHARSET
);
277 * Write the Strings entry (1st byte = size, then the bytes, then
278 * the 0). We have checked the string length at the constructor.
280 buffer
.putShort((short) strArray
.length
);
281 buffer
.put(strArray
);
282 buffer
.put((byte) 0);
287 buffer
.putLong(sv
.unboxLong());
291 buffer
.putDouble(sv
.unboxDouble());
295 int size
= ((CustomStateValue
) sv
).getSerializedSize();
296 buffer
.putShort((short) size
);
297 ISafeByteBufferWriter safeBuffer
= SafeByteBufferFactory
.wrapWriter(buffer
, size
);
298 ((CustomStateValue
) sv
).serialize(safeBuffer
);
308 public long getStartTime() {
313 public long getEndTime() {
318 public int getAttribute() {
323 public ITmfStateValue
getStateValue() {
328 public boolean intersects(long timestamp
) {
329 if (start
<= timestamp
) {
330 if (end
>= timestamp
) {
338 * Total serialized size of this interval
340 * @return The interval size
342 public int getSizeOnDisk() {
347 public boolean equals(Object obj
) {
354 if (getClass() != obj
.getClass()) {
357 HTInterval other
= (HTInterval
) obj
;
358 return (start
== other
.start
&&
360 attribute
== other
.attribute
&&
361 sv
.equals(other
.sv
));
365 public int hashCode() {
366 return Objects
.hash(start
, end
, attribute
, sv
);
370 public String
toString() {
371 /* Only for debug, should not be externalized */
372 StringBuilder sb
= new StringBuilder();
375 sb
.append(", "); //$NON-NLS-1$
379 sb
.append(", attribute = "); //$NON-NLS-1$
380 sb
.append(attribute
);
382 sb
.append(", value = "); //$NON-NLS-1$
383 sb
.append(sv
.toString());
385 return sb
.toString();
389 * Here we determine how state values "types" are written in the 8-bit field
390 * that indicates the value type in the file.
392 private static byte getByteFromType(ITmfStateValue
.Type type
) {
407 /* Should not happen if the switch is fully covered */
408 throw new IllegalStateException();