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