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