Commit | Line | Data |
---|---|---|
a52fde77 | 1 | /******************************************************************************* |
e13bd4cd | 2 | * Copyright (c) 2012, 2015 Ericsson, École Polytechnique de Montréal |
a52fde77 | 3 | * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com> |
98107b3f | 4 | * |
a52fde77 AM |
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 | |
98107b3f | 9 | * |
b0136ad6 FW |
10 | * Contributors: |
11 | * Alexandre Montplaisir - Initial API and implementation | |
12 | * Florian Wininger - Allow to change the size of a interval | |
e13bd4cd | 13 | * Patrick Tasse - Add message to exceptions |
a52fde77 AM |
14 | *******************************************************************************/ |
15 | ||
e894a508 | 16 | package org.eclipse.tracecompass.internal.statesystem.core.backend.historytree; |
a52fde77 AM |
17 | |
18 | import java.io.IOException; | |
19 | import java.nio.ByteBuffer; | |
20 | ||
e894a508 AM |
21 | import org.eclipse.tracecompass.statesystem.core.exceptions.StateValueTypeException; |
22 | import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException; | |
23 | import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval; | |
24 | import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue; | |
25 | import org.eclipse.tracecompass.statesystem.core.statevalue.TmfStateValue; | |
a52fde77 AM |
26 | |
27 | /** | |
28 | * The interval component, which will be contained in a node of the History | |
29 | * Tree. | |
98107b3f | 30 | * |
8d47cc34 | 31 | * @author Alexandre Montplaisir |
a52fde77 | 32 | */ |
8d47cc34 | 33 | public final class HTInterval implements ITmfStateInterval, Comparable<HTInterval> { |
a52fde77 | 34 | |
b67a2540 AM |
35 | private static final String errMsg = "Invalid interval data. Maybe your file is corrupt?"; //$NON-NLS-1$ |
36 | ||
b0136ad6 FW |
37 | /** |
38 | * Size of an entry in the data section. | |
39 | * | |
40 | * <pre> | |
41 | * 16 2 x Timevalue/long (interval start + end) | |
42 | * + 4 int (key) | |
43 | * + 1 byte (type) | |
44 | * + 4 int (valueOffset) | |
45 | * </pre> | |
46 | */ | |
47 | private static final int DATA_ENTRY_SIZE = 25; | |
48 | ||
b67a2540 AM |
49 | /* 'Byte' equivalent for state values types */ |
50 | private static final byte TYPE_NULL = -1; | |
51 | private static final byte TYPE_INTEGER = 0; | |
52 | private static final byte TYPE_STRING = 1; | |
1cbf1a19 | 53 | private static final byte TYPE_LONG = 2; |
a3c22e8e | 54 | private static final byte TYPE_DOUBLE = 3; |
b67a2540 | 55 | |
b4603a60 AM |
56 | /* String entry sizes of different state values */ |
57 | private static final int NO_ENTRY_SIZE = 0; | |
58 | private static final int LONG_ENTRY_SIZE = 8; | |
a3c22e8e | 59 | private static final int DOUBLE_ENTRY_SIZE = 8; |
b4603a60 AM |
60 | // sizes of string values depend on the string itself |
61 | ||
a52fde77 AM |
62 | private final long start; |
63 | private final long end; | |
64 | private final int attribute; | |
65 | private final TmfStateValue sv; | |
66 | ||
67 | /* | |
68 | * Size of the strings section entry used by this interval (= 0 if not used) | |
69 | */ | |
70 | private final int stringsEntrySize; | |
71 | ||
72 | /** | |
73 | * Standard constructor | |
98107b3f | 74 | * |
a52fde77 | 75 | * @param intervalStart |
8d47cc34 | 76 | * Start time of the interval |
a52fde77 | 77 | * @param intervalEnd |
8d47cc34 | 78 | * End time of the interval |
a52fde77 | 79 | * @param attribute |
8d47cc34 AM |
80 | * Attribute (quark) to which the state represented by this |
81 | * interval belongs | |
a52fde77 | 82 | * @param value |
8d47cc34 | 83 | * State value represented by this interval |
a52fde77 | 84 | * @throws TimeRangeException |
8d47cc34 | 85 | * If the start time or end time are invalid |
a52fde77 | 86 | */ |
8d47cc34 | 87 | public HTInterval(long intervalStart, long intervalEnd, int attribute, |
a52fde77 AM |
88 | TmfStateValue value) throws TimeRangeException { |
89 | if (intervalStart > intervalEnd) { | |
e13bd4cd | 90 | throw new TimeRangeException("Start:" + intervalStart + ", End:" + intervalEnd); //$NON-NLS-1$ //$NON-NLS-2$ |
a52fde77 AM |
91 | } |
92 | ||
93 | this.start = intervalStart; | |
94 | this.end = intervalEnd; | |
95 | this.attribute = attribute; | |
96 | this.sv = value; | |
97 | this.stringsEntrySize = computeStringsEntrySize(); | |
98 | } | |
99 | ||
b4603a60 AM |
100 | /** |
101 | * "Faster" constructor for inner use only. When we build an interval when | |
102 | * reading it from disk (with {@link #readFrom}), we already know the size | |
103 | * of the strings entry, so there is no need to call | |
104 | * {@link #computeStringsEntrySize()} and do an extra copy. | |
b4603a60 AM |
105 | */ |
106 | private HTInterval(long intervalStart, long intervalEnd, int attribute, | |
107 | TmfStateValue value, int size) throws TimeRangeException { | |
108 | if (intervalStart > intervalEnd) { | |
e13bd4cd | 109 | throw new TimeRangeException("Start:" + intervalStart + ", End:" + intervalEnd); //$NON-NLS-1$ //$NON-NLS-2$ |
b4603a60 AM |
110 | } |
111 | ||
112 | this.start = intervalStart; | |
113 | this.end = intervalEnd; | |
114 | this.attribute = attribute; | |
115 | this.sv = value; | |
116 | this.stringsEntrySize = size; | |
117 | } | |
118 | ||
a52fde77 | 119 | /** |
8d47cc34 | 120 | * Reader factory method. Builds the interval using an already-allocated |
a52fde77 | 121 | * ByteBuffer, which normally comes from a NIO FileChannel. |
98107b3f | 122 | * |
a52fde77 AM |
123 | * @param buffer |
124 | * The ByteBuffer from which to read the information | |
8d47cc34 | 125 | * @return The interval object |
a52fde77 | 126 | * @throws IOException |
8d47cc34 | 127 | * If there was an error reading from the buffer |
a52fde77 | 128 | */ |
8d47cc34 | 129 | public static final HTInterval readFrom(ByteBuffer buffer) throws IOException { |
a52fde77 AM |
130 | HTInterval interval; |
131 | long intervalStart, intervalEnd; | |
132 | int attribute; | |
133 | TmfStateValue value; | |
134 | int valueOrOffset, valueSize, res; | |
135 | byte valueType; | |
136 | byte array[]; | |
137 | ||
138 | /* Read the Data Section entry */ | |
139 | intervalStart = buffer.getLong(); | |
140 | intervalEnd = buffer.getLong(); | |
141 | attribute = buffer.getInt(); | |
142 | ||
143 | /* Read the 'type' of the value, then react accordingly */ | |
144 | valueType = buffer.get(); | |
b67a2540 AM |
145 | valueOrOffset = buffer.getInt(); |
146 | switch (valueType) { | |
a52fde77 | 147 | |
b67a2540 AM |
148 | case TYPE_NULL: |
149 | value = TmfStateValue.nullValue(); | |
b4603a60 | 150 | valueSize = NO_ENTRY_SIZE; |
b67a2540 | 151 | break; |
a52fde77 | 152 | |
b67a2540 AM |
153 | case TYPE_INTEGER: |
154 | /* "ValueOrOffset" is the straight value */ | |
155 | value = TmfStateValue.newValueInt(valueOrOffset); | |
b4603a60 | 156 | valueSize = NO_ENTRY_SIZE; |
b67a2540 AM |
157 | break; |
158 | ||
159 | case TYPE_STRING: | |
160 | /* Go read the matching entry in the Strings section of the block */ | |
a52fde77 AM |
161 | buffer.mark(); |
162 | buffer.position(valueOrOffset); | |
163 | ||
164 | /* the first byte = the size to read */ | |
165 | valueSize = buffer.get(); | |
166 | ||
167 | /* | |
168 | * Careful though, 'valueSize' is the total size of the entry, | |
169 | * including the 'size' byte at the start and end (0'ed) byte at the | |
170 | * end. Here we want 'array' to only contain the real payload of the | |
171 | * value. | |
172 | */ | |
173 | array = new byte[valueSize - 2]; | |
174 | buffer.get(array); | |
175 | value = TmfStateValue.newValueString(new String(array)); | |
176 | ||
177 | /* Confirm the 0'ed byte at the end */ | |
178 | res = buffer.get(); | |
179 | if (res != 0) { | |
b67a2540 | 180 | throw new IOException(errMsg); |
a52fde77 AM |
181 | } |
182 | ||
1cbf1a19 FR |
183 | /* |
184 | * Restore the file pointer's position (so we can read the next | |
185 | * interval) | |
186 | */ | |
187 | buffer.reset(); | |
188 | break; | |
189 | ||
190 | case TYPE_LONG: | |
191 | /* Go read the matching entry in the Strings section of the block */ | |
192 | buffer.mark(); | |
193 | buffer.position(valueOrOffset); | |
194 | value = TmfStateValue.newValueLong(buffer.getLong()); | |
b4603a60 | 195 | valueSize = LONG_ENTRY_SIZE; |
1cbf1a19 | 196 | |
a52fde77 AM |
197 | /* |
198 | * Restore the file pointer's position (so we can read the next | |
199 | * interval) | |
200 | */ | |
201 | buffer.reset(); | |
b67a2540 | 202 | break; |
a3c22e8e AM |
203 | |
204 | case TYPE_DOUBLE: | |
205 | /* Go read the matching entry in the Strings section of the block */ | |
206 | buffer.mark(); | |
207 | buffer.position(valueOrOffset); | |
208 | value = TmfStateValue.newValueDouble(buffer.getDouble()); | |
209 | valueSize = DOUBLE_ENTRY_SIZE; | |
210 | ||
211 | /* | |
212 | * Restore the file pointer's position (so we can read the next | |
213 | * interval) | |
214 | */ | |
215 | buffer.reset(); | |
216 | break; | |
217 | ||
b67a2540 AM |
218 | default: |
219 | /* Unknown data, better to not make anything up... */ | |
220 | throw new IOException(errMsg); | |
a52fde77 AM |
221 | } |
222 | ||
223 | try { | |
b4603a60 | 224 | interval = new HTInterval(intervalStart, intervalEnd, attribute, value, valueSize); |
a52fde77 | 225 | } catch (TimeRangeException e) { |
b67a2540 | 226 | throw new IOException(errMsg); |
a52fde77 AM |
227 | } |
228 | return interval; | |
229 | } | |
230 | ||
231 | /** | |
232 | * Antagonist of the previous constructor, write the Data entry | |
233 | * corresponding to this interval in a ByteBuffer (mapped to a block in the | |
234 | * history-file, hopefully) | |
98107b3f | 235 | * |
a52fde77 AM |
236 | * @param buffer |
237 | * The already-allocated ByteBuffer corresponding to a SHT Node | |
238 | * @param endPosOfStringEntry | |
239 | * The initial (before calling this function for this interval) | |
240 | * position of the Strings Entry for this node. This will change | |
241 | * from one call to the other if we're writing String | |
242 | * StateValues. | |
243 | * @return The size of the Strings Entry that was written, if any. | |
244 | */ | |
8d47cc34 | 245 | public int writeInterval(ByteBuffer buffer, int endPosOfStringEntry) { |
a52fde77 AM |
246 | buffer.putLong(start); |
247 | buffer.putLong(end); | |
248 | buffer.putInt(attribute); | |
b67a2540 | 249 | buffer.put(getByteFromType(sv.getType())); |
a52fde77 | 250 | |
1cbf1a19 | 251 | switch (getByteFromType(sv.getType())) { |
a52fde77 | 252 | |
1cbf1a19 FR |
253 | case TYPE_NULL: |
254 | case TYPE_INTEGER: | |
359eeba0 | 255 | /* We write the 'valueOffset' field as a straight value. */ |
6a4fd492 AM |
256 | try { |
257 | buffer.putInt(sv.unboxInt()); | |
258 | } catch (StateValueTypeException e) { | |
259 | /* | |
260 | * This should not happen, since the value told us it was of | |
261 | * type Null or Integer (corrupted value?) | |
262 | */ | |
263 | e.printStackTrace(); | |
a52fde77 | 264 | } |
9177eb74 | 265 | break; |
a52fde77 | 266 | |
1cbf1a19 | 267 | case TYPE_STRING: |
9177eb74 AM |
268 | byte[] byteArrayToWrite; |
269 | try { | |
270 | byteArrayToWrite = sv.unboxStr().getBytes(); | |
271 | } catch (StateValueTypeException e1) { | |
272 | /* Should not happen, we're in a switch/case for string type */ | |
273 | throw new RuntimeException(); | |
274 | } | |
1cbf1a19 FR |
275 | |
276 | /* we use the valueOffset as an offset. */ | |
9177eb74 | 277 | buffer.putInt(endPosOfStringEntry - stringsEntrySize); |
1cbf1a19 | 278 | buffer.mark(); |
9177eb74 | 279 | buffer.position(endPosOfStringEntry - stringsEntrySize); |
1cbf1a19 FR |
280 | |
281 | /* | |
282 | * write the Strings entry (1st byte = size, then the bytes, then the 0) | |
283 | */ | |
9177eb74 | 284 | buffer.put((byte) stringsEntrySize); |
1cbf1a19 FR |
285 | buffer.put(byteArrayToWrite); |
286 | buffer.put((byte) 0); | |
287 | assert (buffer.position() == endPosOfStringEntry); | |
288 | buffer.reset(); | |
9177eb74 | 289 | break; |
1cbf1a19 FR |
290 | |
291 | case TYPE_LONG: | |
1cbf1a19 | 292 | /* we use the valueOffset as an offset. */ |
9177eb74 | 293 | buffer.putInt(endPosOfStringEntry - stringsEntrySize); |
1cbf1a19 | 294 | buffer.mark(); |
9177eb74 | 295 | buffer.position(endPosOfStringEntry - stringsEntrySize); |
1cbf1a19 FR |
296 | |
297 | /* | |
298 | * write the Long in the Strings section | |
299 | */ | |
300 | try { | |
301 | buffer.putLong(sv.unboxLong()); | |
302 | } catch (StateValueTypeException e) { | |
303 | /* | |
304 | * This should not happen, since the value told us it was of | |
305 | * type Long (corrupted value?) | |
306 | */ | |
307 | e.printStackTrace(); | |
308 | } | |
309 | assert (buffer.position() == endPosOfStringEntry); | |
310 | buffer.reset(); | |
9177eb74 | 311 | break; |
1cbf1a19 | 312 | |
a3c22e8e AM |
313 | case TYPE_DOUBLE: |
314 | /* we use the valueOffset as an offset. */ | |
315 | buffer.putInt(endPosOfStringEntry - stringsEntrySize); | |
316 | buffer.mark(); | |
317 | buffer.position(endPosOfStringEntry - stringsEntrySize); | |
318 | ||
319 | /* Write the Double in the Strings section */ | |
320 | try { | |
321 | buffer.putDouble(sv.unboxDouble()); | |
322 | } catch (StateValueTypeException e) { | |
323 | /* | |
324 | * This should not happen, since the value told us it was of | |
325 | * type Double (corrupted value?) | |
326 | */ | |
327 | e.printStackTrace(); | |
328 | } | |
329 | if (buffer.position() != endPosOfStringEntry) { | |
330 | throw new IllegalStateException(); | |
331 | } | |
332 | buffer.reset(); | |
333 | break; | |
334 | ||
1cbf1a19 | 335 | default: |
9177eb74 | 336 | break; |
a52fde77 | 337 | } |
9177eb74 | 338 | return stringsEntrySize; |
a52fde77 AM |
339 | } |
340 | ||
341 | @Override | |
342 | public long getStartTime() { | |
343 | return start; | |
344 | } | |
345 | ||
346 | @Override | |
347 | public long getEndTime() { | |
348 | return end; | |
349 | } | |
350 | ||
eaad89a0 AM |
351 | @Override |
352 | public long getViewerEndTime() { | |
353 | return end + 1; | |
354 | } | |
355 | ||
a52fde77 AM |
356 | @Override |
357 | public int getAttribute() { | |
358 | return attribute; | |
359 | } | |
360 | ||
361 | @Override | |
362 | public ITmfStateValue getStateValue() { | |
363 | return sv; | |
364 | } | |
365 | ||
366 | @Override | |
367 | public boolean intersects(long timestamp) { | |
368 | if (start <= timestamp) { | |
369 | if (end >= timestamp) { | |
370 | return true; | |
371 | } | |
372 | } | |
373 | return false; | |
374 | } | |
375 | ||
376 | int getStringsEntrySize() { | |
377 | return stringsEntrySize; | |
378 | } | |
379 | ||
380 | /** | |
381 | * Total serialized size of this interval | |
98107b3f | 382 | * |
8d47cc34 | 383 | * @return The interval size |
a52fde77 | 384 | */ |
8d47cc34 | 385 | public int getIntervalSize() { |
b0136ad6 | 386 | return stringsEntrySize + DATA_ENTRY_SIZE; |
a52fde77 AM |
387 | } |
388 | ||
389 | private int computeStringsEntrySize() { | |
9177eb74 AM |
390 | switch(sv.getType()) { |
391 | case NULL: | |
392 | case INTEGER: | |
393 | /* Those don't use the strings section at all */ | |
b4603a60 | 394 | return NO_ENTRY_SIZE; |
9177eb74 AM |
395 | case LONG: |
396 | /* The value's bytes are written directly into the strings section */ | |
b4603a60 | 397 | return LONG_ENTRY_SIZE; |
a3c22e8e AM |
398 | case DOUBLE: |
399 | /* The value is also written directly into the strings section */ | |
400 | return DOUBLE_ENTRY_SIZE; | |
9177eb74 AM |
401 | case STRING: |
402 | try { | |
403 | /* String's length + 2 (1 byte for size, 1 byte for \0 at the end */ | |
404 | return sv.unboxStr().getBytes().length + 2; | |
405 | } catch (StateValueTypeException e) { | |
406 | /* We're inside a switch/case for the string type, can't happen */ | |
cb42195c | 407 | throw new IllegalStateException(e); |
9177eb74 AM |
408 | } |
409 | default: | |
410 | /* It's very important that we know how to write the state value in | |
411 | * the file!! */ | |
cb42195c | 412 | throw new IllegalStateException(); |
1b558482 | 413 | } |
a52fde77 AM |
414 | } |
415 | ||
416 | /** | |
417 | * Compare the END TIMES of different intervals. This is used to sort the | |
418 | * intervals when we close down a node. | |
419 | */ | |
420 | @Override | |
421 | public int compareTo(HTInterval other) { | |
422 | if (this.end < other.end) { | |
423 | return -1; | |
424 | } else if (this.end > other.end) { | |
425 | return 1; | |
426 | } else { | |
427 | return 0; | |
428 | } | |
429 | } | |
1b558482 | 430 | |
ab604305 AM |
431 | @Override |
432 | public boolean equals(Object other) { | |
cb42195c AM |
433 | if (other instanceof HTInterval && |
434 | this.compareTo((HTInterval) other) == 0) { | |
435 | return true; | |
ab604305 AM |
436 | } |
437 | return false; | |
438 | } | |
439 | ||
440 | @Override | |
441 | public int hashCode() { | |
442 | return super.hashCode(); | |
443 | } | |
1b558482 AM |
444 | |
445 | @Override | |
446 | public String toString() { | |
447 | /* Only for debug, should not be externalized */ | |
98107b3f AM |
448 | StringBuilder sb = new StringBuilder(); |
449 | sb.append('['); | |
450 | sb.append(start); | |
451 | sb.append(", "); //$NON-NLS-1$ | |
452 | sb.append(end); | |
453 | sb.append(']'); | |
454 | ||
455 | sb.append(", attribute = "); //$NON-NLS-1$ | |
456 | sb.append(attribute); | |
457 | ||
458 | sb.append(", value = "); //$NON-NLS-1$ | |
459 | sb.append(sv.toString()); | |
460 | ||
461 | return sb.toString(); | |
1b558482 | 462 | } |
b67a2540 AM |
463 | |
464 | /** | |
465 | * Here we determine how state values "types" are written in the 8-bit | |
466 | * field that indicates the value type in the file. | |
467 | */ | |
468 | private static byte getByteFromType(ITmfStateValue.Type type) { | |
469 | switch(type) { | |
470 | case NULL: | |
471 | return TYPE_NULL; | |
472 | case INTEGER: | |
473 | return TYPE_INTEGER; | |
474 | case STRING: | |
475 | return TYPE_STRING; | |
1cbf1a19 FR |
476 | case LONG: |
477 | return TYPE_LONG; | |
a3c22e8e AM |
478 | case DOUBLE: |
479 | return TYPE_DOUBLE; | |
b67a2540 AM |
480 | default: |
481 | /* Should not happen if the switch is fully covered */ | |
cb42195c | 482 | throw new IllegalStateException(); |
b67a2540 AM |
483 | } |
484 | } | |
a52fde77 | 485 | } |