e0ab848711d4370b4d53376ef9bfc78d68324c8a
[deliverable/tracecompass.git] / statesystem / org.eclipse.tracecompass.statesystem.core / src / org / eclipse / tracecompass / internal / statesystem / core / backend / historytree / HTInterval.java
1 /*******************************************************************************
2 * Copyright (c) 2012, 2015 Ericsson, École Polytechnique de Montréal
3 * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
4 *
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
9 *
10 * Contributors:
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 *******************************************************************************/
15
16 package org.eclipse.tracecompass.internal.statesystem.core.backend.historytree;
17
18 import java.io.IOException;
19 import java.nio.ByteBuffer;
20 import java.nio.charset.Charset;
21 import java.util.Objects;
22
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;
32
33 /**
34 * The interval component, which will be contained in a node of the History
35 * Tree.
36 *
37 * @author Alexandre Montplaisir
38 */
39 public final class HTInterval implements ITmfStateInterval {
40
41 private static final Charset CHARSET = Charset.forName("UTF-8"); //$NON-NLS-1$
42
43 private static final String errMsg = "Invalid interval data. Maybe your file is corrupt?"; //$NON-NLS-1$
44
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;
52
53 private final long start;
54 private final long end;
55 private final int attribute;
56 private final @NonNull TmfStateValue sv;
57
58 /** Number of bytes used by this interval when it is written to disk */
59 private final int fSizeOnDisk;
60
61 /**
62 * Standard constructor
63 *
64 * @param intervalStart
65 * Start time of the interval
66 * @param intervalEnd
67 * End time of the interval
68 * @param attribute
69 * Attribute (quark) to which the state represented by this
70 * interval belongs
71 * @param value
72 * State value represented by this interval
73 * @throws TimeRangeException
74 * If the start time or end time are invalid
75 */
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$
80 }
81
82 this.start = intervalStart;
83 this.end = intervalEnd;
84 this.attribute = attribute;
85 this.sv = value;
86 this.fSizeOnDisk = computeSizeOnDisk(sv);
87 }
88
89 /**
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.
92 */
93 private static int computeSizeOnDisk(ITmfStateValue sv) {
94 /*
95 * Minimum size is 2x long (start and end), 1x int (attribute) and 1x
96 * byte (value type).
97 */
98 int minSize = Long.BYTES + Long.BYTES + Integer.BYTES + Byte.BYTES;
99
100 switch (sv.getType()) {
101 case NULL:
102 return minSize;
103 case INTEGER:
104 return (minSize + Integer.BYTES);
105 case LONG:
106 return (minSize + Long.BYTES);
107 case DOUBLE:
108 return (minSize + Double.BYTES);
109 case STRING:
110 String str = sv.unboxStr();
111 int strLength = str.getBytes(CHARSET).length;
112
113 if (strLength > Short.MAX_VALUE) {
114 throw new IllegalArgumentException("String is too long to be stored in state system: " + str); //$NON-NLS-1$
115 }
116
117 /*
118 * String's length + 3 (2 bytes for size, 1 byte for \0 at the end)
119 */
120 return (minSize + strLength + 3);
121 case CUSTOM:
122 /* Length of serialized value (short) + state value */
123 return (minSize + Short.BYTES + ((CustomStateValue) sv).getSerializedSize());
124 default:
125 /*
126 * It's very important that we know how to write the state value in
127 * the file!!
128 */
129 throw new IllegalStateException();
130 }
131 }
132
133 /**
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.
138 */
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$
143 }
144
145 this.start = intervalStart;
146 this.end = intervalEnd;
147 this.attribute = attribute;
148 this.sv = value;
149 this.fSizeOnDisk = size;
150 }
151
152 /**
153 * Reader factory method. Builds the interval using an already-allocated
154 * ByteBuffer, which normally comes from a NIO FileChannel.
155 *
156 * The interval is just a start, end, attribute and value, this is the
157 * layout of the HTInterval on disk
158 * <ul>
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>
165 * </ul>
166 *
167 * @param buffer
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
172 */
173 public static final HTInterval readFrom(ByteBuffer buffer) throws IOException {
174 TmfStateValue value;
175
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();
181
182 /* Read the 'type' of the value, then react accordingly */
183 byte valueType = buffer.get();
184 switch (valueType) {
185
186 case TYPE_NULL:
187 value = TmfStateValue.nullValue();
188 break;
189
190 case TYPE_INTEGER:
191 value = TmfStateValue.newValueInt(buffer.getInt());
192 break;
193
194 case TYPE_STRING: {
195 /* the first short = the size to read */
196 int valueSize = buffer.getShort();
197
198 byte[] array = new byte[valueSize];
199 buffer.get(array);
200 value = TmfStateValue.newValueString(new String(array, CHARSET));
201
202 /* Confirm the 0'ed byte at the end */
203 byte res = buffer.get();
204 if (res != 0) {
205 throw new IOException(errMsg);
206 }
207 break;
208 }
209
210 case TYPE_LONG:
211 /* Go read the matching entry in the Strings section of the block */
212 value = TmfStateValue.newValueLong(buffer.getLong());
213 break;
214
215 case TYPE_DOUBLE:
216 /* Go read the matching entry in the Strings section of the block */
217 value = TmfStateValue.newValueDouble(buffer.getDouble());
218 break;
219
220 case TYPE_CUSTOM: {
221 short valueSize = buffer.getShort();
222 ISafeByteBufferReader safeBuffer = SafeByteBufferFactory.wrapReader(buffer, valueSize);
223 value = CustomStateValue.readSerializedValue(safeBuffer);
224 break;
225 }
226 default:
227 /* Unknown data, better to not make anything up... */
228 throw new IOException(errMsg);
229 }
230
231 try {
232 return new HTInterval(intervalStart, intervalEnd, attribute, value, buffer.position() - posStart);
233 } catch (TimeRangeException e) {
234 throw new IOException(errMsg);
235 }
236 }
237
238 /**
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)
242 *
243 * The interval is just a start, end, attribute and value, this is the
244 * layout of the HTInterval on disk
245 * <ul>
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>
252 * </ul>
253 *
254 * @param buffer
255 * The already-allocated ByteBuffer corresponding to a SHT Node
256 */
257 public void writeInterval(ByteBuffer buffer) {
258 final byte byteFromType = getByteFromType(sv.getType());
259
260 buffer.putLong(start);
261 buffer.putLong(end);
262 buffer.putInt(attribute);
263 buffer.put(byteFromType);
264
265 switch (byteFromType) {
266 case TYPE_NULL:
267 break;
268 case TYPE_INTEGER:
269 buffer.putInt(sv.unboxInt());
270 break;
271
272 case TYPE_STRING: {
273 String string = sv.unboxStr();
274 byte[] strArray = string.getBytes(CHARSET);
275
276 /*
277 * Write the Strings entry (1st byte = size, then the bytes, then
278 * the 0). We have checked the string length at the constructor.
279 */
280 buffer.putShort((short) strArray.length);
281 buffer.put(strArray);
282 buffer.put((byte) 0);
283 break;
284 }
285
286 case TYPE_LONG:
287 buffer.putLong(sv.unboxLong());
288 break;
289
290 case TYPE_DOUBLE:
291 buffer.putDouble(sv.unboxDouble());
292 break;
293
294 case TYPE_CUSTOM: {
295 int size = ((CustomStateValue) sv).getSerializedSize();
296 buffer.putShort((short) size);
297 ISafeByteBufferWriter safeBuffer = SafeByteBufferFactory.wrapWriter(buffer, size);
298 ((CustomStateValue) sv).serialize(safeBuffer);
299 break;
300 }
301
302 default:
303 break;
304 }
305 }
306
307 @Override
308 public long getStartTime() {
309 return start;
310 }
311
312 @Override
313 public long getEndTime() {
314 return end;
315 }
316
317 @Override
318 public int getAttribute() {
319 return attribute;
320 }
321
322 @Override
323 public ITmfStateValue getStateValue() {
324 return sv;
325 }
326
327 @Override
328 public boolean intersects(long timestamp) {
329 if (start <= timestamp) {
330 if (end >= timestamp) {
331 return true;
332 }
333 }
334 return false;
335 }
336
337 /**
338 * Total serialized size of this interval
339 *
340 * @return The interval size
341 */
342 public int getSizeOnDisk() {
343 return fSizeOnDisk;
344 }
345
346 @Override
347 public boolean equals(Object obj) {
348 if (this == obj) {
349 return true;
350 }
351 if (obj == null) {
352 return false;
353 }
354 if (getClass() != obj.getClass()) {
355 return false;
356 }
357 HTInterval other = (HTInterval) obj;
358 return (start == other.start &&
359 end == other.end &&
360 attribute == other.attribute &&
361 sv.equals(other.sv));
362 }
363
364 @Override
365 public int hashCode() {
366 return Objects.hash(start, end, attribute, sv);
367 }
368
369 @Override
370 public String toString() {
371 /* Only for debug, should not be externalized */
372 StringBuilder sb = new StringBuilder();
373 sb.append('[');
374 sb.append(start);
375 sb.append(", "); //$NON-NLS-1$
376 sb.append(end);
377 sb.append(']');
378
379 sb.append(", attribute = "); //$NON-NLS-1$
380 sb.append(attribute);
381
382 sb.append(", value = "); //$NON-NLS-1$
383 sb.append(sv.toString());
384
385 return sb.toString();
386 }
387
388 /**
389 * Here we determine how state values "types" are written in the 8-bit field
390 * that indicates the value type in the file.
391 */
392 private static byte getByteFromType(ITmfStateValue.Type type) {
393 switch (type) {
394 case NULL:
395 return TYPE_NULL;
396 case INTEGER:
397 return TYPE_INTEGER;
398 case STRING:
399 return TYPE_STRING;
400 case LONG:
401 return TYPE_LONG;
402 case DOUBLE:
403 return TYPE_DOUBLE;
404 case CUSTOM:
405 return TYPE_CUSTOM;
406 default:
407 /* Should not happen if the switch is fully covered */
408 throw new IllegalStateException();
409 }
410 }
411 }
This page took 0.040002 seconds and 4 git commands to generate.