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; |
b67a2540 | 40 | |
a52fde77 AM |
41 | private final long start; |
42 | private final long end; | |
43 | private final int attribute; | |
44 | private final TmfStateValue sv; | |
45 | ||
46 | /* | |
47 | * Size of the strings section entry used by this interval (= 0 if not used) | |
48 | */ | |
49 | private final int stringsEntrySize; | |
50 | ||
51 | /** | |
52 | * Standard constructor | |
98107b3f | 53 | * |
a52fde77 AM |
54 | * @param intervalStart |
55 | * @param intervalEnd | |
56 | * @param attribute | |
57 | * @param value | |
58 | * @throws TimeRangeException | |
59 | */ | |
60 | HTInterval(long intervalStart, long intervalEnd, int attribute, | |
61 | TmfStateValue value) throws TimeRangeException { | |
62 | if (intervalStart > intervalEnd) { | |
63 | throw new TimeRangeException(); | |
64 | } | |
65 | ||
66 | this.start = intervalStart; | |
67 | this.end = intervalEnd; | |
68 | this.attribute = attribute; | |
69 | this.sv = value; | |
70 | this.stringsEntrySize = computeStringsEntrySize(); | |
71 | } | |
72 | ||
73 | /** | |
74 | * Reader constructor. Builds the interval using an already-allocated | |
75 | * ByteBuffer, which normally comes from a NIO FileChannel. | |
98107b3f | 76 | * |
a52fde77 AM |
77 | * @param buffer |
78 | * The ByteBuffer from which to read the information | |
79 | * @throws IOException | |
80 | */ | |
81 | final static HTInterval readFrom(ByteBuffer buffer) throws IOException { | |
82 | HTInterval interval; | |
83 | long intervalStart, intervalEnd; | |
84 | int attribute; | |
85 | TmfStateValue value; | |
86 | int valueOrOffset, valueSize, res; | |
87 | byte valueType; | |
88 | byte array[]; | |
89 | ||
90 | /* Read the Data Section entry */ | |
91 | intervalStart = buffer.getLong(); | |
92 | intervalEnd = buffer.getLong(); | |
93 | attribute = buffer.getInt(); | |
94 | ||
95 | /* Read the 'type' of the value, then react accordingly */ | |
96 | valueType = buffer.get(); | |
b67a2540 AM |
97 | valueOrOffset = buffer.getInt(); |
98 | switch (valueType) { | |
a52fde77 | 99 | |
b67a2540 AM |
100 | case TYPE_NULL: |
101 | value = TmfStateValue.nullValue(); | |
102 | break; | |
a52fde77 | 103 | |
b67a2540 AM |
104 | case TYPE_INTEGER: |
105 | /* "ValueOrOffset" is the straight value */ | |
106 | value = TmfStateValue.newValueInt(valueOrOffset); | |
107 | break; | |
108 | ||
109 | case TYPE_STRING: | |
110 | /* Go read the matching entry in the Strings section of the block */ | |
a52fde77 AM |
111 | buffer.mark(); |
112 | buffer.position(valueOrOffset); | |
113 | ||
114 | /* the first byte = the size to read */ | |
115 | valueSize = buffer.get(); | |
116 | ||
117 | /* | |
118 | * Careful though, 'valueSize' is the total size of the entry, | |
119 | * including the 'size' byte at the start and end (0'ed) byte at the | |
120 | * end. Here we want 'array' to only contain the real payload of the | |
121 | * value. | |
122 | */ | |
123 | array = new byte[valueSize - 2]; | |
124 | buffer.get(array); | |
125 | value = TmfStateValue.newValueString(new String(array)); | |
126 | ||
127 | /* Confirm the 0'ed byte at the end */ | |
128 | res = buffer.get(); | |
129 | if (res != 0) { | |
b67a2540 | 130 | throw new IOException(errMsg); |
a52fde77 AM |
131 | } |
132 | ||
1cbf1a19 FR |
133 | /* |
134 | * Restore the file pointer's position (so we can read the next | |
135 | * interval) | |
136 | */ | |
137 | buffer.reset(); | |
138 | break; | |
139 | ||
140 | case TYPE_LONG: | |
141 | /* Go read the matching entry in the Strings section of the block */ | |
142 | buffer.mark(); | |
143 | buffer.position(valueOrOffset); | |
144 | value = TmfStateValue.newValueLong(buffer.getLong()); | |
145 | ||
a52fde77 AM |
146 | /* |
147 | * Restore the file pointer's position (so we can read the next | |
148 | * interval) | |
149 | */ | |
150 | buffer.reset(); | |
b67a2540 AM |
151 | break; |
152 | default: | |
153 | /* Unknown data, better to not make anything up... */ | |
154 | throw new IOException(errMsg); | |
a52fde77 AM |
155 | } |
156 | ||
157 | try { | |
b67a2540 | 158 | interval = new HTInterval(intervalStart, intervalEnd, attribute, value); |
a52fde77 | 159 | } catch (TimeRangeException e) { |
b67a2540 | 160 | throw new IOException(errMsg); |
a52fde77 AM |
161 | } |
162 | return interval; | |
163 | } | |
164 | ||
165 | /** | |
166 | * Antagonist of the previous constructor, write the Data entry | |
167 | * corresponding to this interval in a ByteBuffer (mapped to a block in the | |
168 | * history-file, hopefully) | |
98107b3f | 169 | * |
a52fde77 AM |
170 | * @param buffer |
171 | * The already-allocated ByteBuffer corresponding to a SHT Node | |
172 | * @param endPosOfStringEntry | |
173 | * The initial (before calling this function for this interval) | |
174 | * position of the Strings Entry for this node. This will change | |
175 | * from one call to the other if we're writing String | |
176 | * StateValues. | |
177 | * @return The size of the Strings Entry that was written, if any. | |
178 | */ | |
179 | int writeInterval(ByteBuffer buffer, int endPosOfStringEntry) { | |
a52fde77 AM |
180 | buffer.putLong(start); |
181 | buffer.putLong(end); | |
182 | buffer.putInt(attribute); | |
b67a2540 | 183 | buffer.put(getByteFromType(sv.getType())); |
a52fde77 | 184 | |
1cbf1a19 | 185 | switch (getByteFromType(sv.getType())) { |
a52fde77 | 186 | |
1cbf1a19 FR |
187 | case TYPE_NULL: |
188 | case TYPE_INTEGER: | |
6a4fd492 AM |
189 | /* We write the 'valueOffset' field as a straight value. In the case |
190 | * of a null value, it will be unboxed as -1 */ | |
191 | try { | |
192 | buffer.putInt(sv.unboxInt()); | |
193 | } catch (StateValueTypeException e) { | |
194 | /* | |
195 | * This should not happen, since the value told us it was of | |
196 | * type Null or Integer (corrupted value?) | |
197 | */ | |
198 | e.printStackTrace(); | |
a52fde77 | 199 | } |
9177eb74 | 200 | break; |
a52fde77 | 201 | |
1cbf1a19 | 202 | case TYPE_STRING: |
9177eb74 AM |
203 | byte[] byteArrayToWrite; |
204 | try { | |
205 | byteArrayToWrite = sv.unboxStr().getBytes(); | |
206 | } catch (StateValueTypeException e1) { | |
207 | /* Should not happen, we're in a switch/case for string type */ | |
208 | throw new RuntimeException(); | |
209 | } | |
1cbf1a19 FR |
210 | |
211 | /* we use the valueOffset as an offset. */ | |
9177eb74 | 212 | buffer.putInt(endPosOfStringEntry - stringsEntrySize); |
1cbf1a19 | 213 | buffer.mark(); |
9177eb74 | 214 | buffer.position(endPosOfStringEntry - stringsEntrySize); |
1cbf1a19 FR |
215 | |
216 | /* | |
217 | * write the Strings entry (1st byte = size, then the bytes, then the 0) | |
218 | */ | |
9177eb74 | 219 | buffer.put((byte) stringsEntrySize); |
1cbf1a19 FR |
220 | buffer.put(byteArrayToWrite); |
221 | buffer.put((byte) 0); | |
222 | assert (buffer.position() == endPosOfStringEntry); | |
223 | buffer.reset(); | |
9177eb74 | 224 | break; |
1cbf1a19 FR |
225 | |
226 | case TYPE_LONG: | |
1cbf1a19 | 227 | /* we use the valueOffset as an offset. */ |
9177eb74 | 228 | buffer.putInt(endPosOfStringEntry - stringsEntrySize); |
1cbf1a19 | 229 | buffer.mark(); |
9177eb74 | 230 | buffer.position(endPosOfStringEntry - stringsEntrySize); |
1cbf1a19 FR |
231 | |
232 | /* | |
233 | * write the Long in the Strings section | |
234 | */ | |
235 | try { | |
236 | buffer.putLong(sv.unboxLong()); | |
237 | } catch (StateValueTypeException e) { | |
238 | /* | |
239 | * This should not happen, since the value told us it was of | |
240 | * type Long (corrupted value?) | |
241 | */ | |
242 | e.printStackTrace(); | |
243 | } | |
244 | assert (buffer.position() == endPosOfStringEntry); | |
245 | buffer.reset(); | |
9177eb74 | 246 | break; |
1cbf1a19 FR |
247 | |
248 | default: | |
9177eb74 | 249 | break; |
a52fde77 | 250 | } |
9177eb74 | 251 | return stringsEntrySize; |
a52fde77 AM |
252 | } |
253 | ||
254 | @Override | |
255 | public long getStartTime() { | |
256 | return start; | |
257 | } | |
258 | ||
259 | @Override | |
260 | public long getEndTime() { | |
261 | return end; | |
262 | } | |
263 | ||
eaad89a0 AM |
264 | @Override |
265 | public long getViewerEndTime() { | |
266 | return end + 1; | |
267 | } | |
268 | ||
a52fde77 AM |
269 | @Override |
270 | public int getAttribute() { | |
271 | return attribute; | |
272 | } | |
273 | ||
274 | @Override | |
275 | public ITmfStateValue getStateValue() { | |
276 | return sv; | |
277 | } | |
278 | ||
279 | @Override | |
280 | public boolean intersects(long timestamp) { | |
281 | if (start <= timestamp) { | |
282 | if (end >= timestamp) { | |
283 | return true; | |
284 | } | |
285 | } | |
286 | return false; | |
287 | } | |
288 | ||
289 | int getStringsEntrySize() { | |
290 | return stringsEntrySize; | |
291 | } | |
292 | ||
293 | /** | |
294 | * Total serialized size of this interval | |
98107b3f | 295 | * |
a52fde77 AM |
296 | * @return |
297 | */ | |
298 | int getIntervalSize() { | |
299 | return stringsEntrySize + HTNode.getDataEntrySize(); | |
300 | } | |
301 | ||
302 | private int computeStringsEntrySize() { | |
9177eb74 AM |
303 | switch(sv.getType()) { |
304 | case NULL: | |
305 | case INTEGER: | |
306 | /* Those don't use the strings section at all */ | |
a52fde77 | 307 | return 0; |
9177eb74 AM |
308 | case LONG: |
309 | /* The value's bytes are written directly into the strings section */ | |
310 | return 8; | |
311 | case STRING: | |
312 | try { | |
313 | /* String's length + 2 (1 byte for size, 1 byte for \0 at the end */ | |
314 | return sv.unboxStr().getBytes().length + 2; | |
315 | } catch (StateValueTypeException e) { | |
316 | /* We're inside a switch/case for the string type, can't happen */ | |
317 | throw new RuntimeException(); | |
318 | } | |
319 | default: | |
320 | /* It's very important that we know how to write the state value in | |
321 | * the file!! */ | |
322 | throw new RuntimeException(); | |
1b558482 | 323 | } |
a52fde77 AM |
324 | } |
325 | ||
326 | /** | |
327 | * Compare the END TIMES of different intervals. This is used to sort the | |
328 | * intervals when we close down a node. | |
329 | */ | |
330 | @Override | |
331 | public int compareTo(HTInterval other) { | |
332 | if (this.end < other.end) { | |
333 | return -1; | |
334 | } else if (this.end > other.end) { | |
335 | return 1; | |
336 | } else { | |
337 | return 0; | |
338 | } | |
339 | } | |
1b558482 | 340 | |
ab604305 AM |
341 | @Override |
342 | public boolean equals(Object other) { | |
343 | if (other instanceof HTInterval) { | |
344 | if (this.compareTo((HTInterval) other) == 0) { | |
345 | return true; | |
346 | } | |
347 | } | |
348 | return false; | |
349 | } | |
350 | ||
351 | @Override | |
352 | public int hashCode() { | |
353 | return super.hashCode(); | |
354 | } | |
1b558482 AM |
355 | |
356 | @Override | |
357 | public String toString() { | |
358 | /* Only for debug, should not be externalized */ | |
98107b3f AM |
359 | StringBuilder sb = new StringBuilder(); |
360 | sb.append('['); | |
361 | sb.append(start); | |
362 | sb.append(", "); //$NON-NLS-1$ | |
363 | sb.append(end); | |
364 | sb.append(']'); | |
365 | ||
366 | sb.append(", attribute = "); //$NON-NLS-1$ | |
367 | sb.append(attribute); | |
368 | ||
369 | sb.append(", value = "); //$NON-NLS-1$ | |
370 | sb.append(sv.toString()); | |
371 | ||
372 | return sb.toString(); | |
1b558482 | 373 | } |
b67a2540 AM |
374 | |
375 | /** | |
376 | * Here we determine how state values "types" are written in the 8-bit | |
377 | * field that indicates the value type in the file. | |
378 | */ | |
379 | private static byte getByteFromType(ITmfStateValue.Type type) { | |
380 | switch(type) { | |
381 | case NULL: | |
382 | return TYPE_NULL; | |
383 | case INTEGER: | |
384 | return TYPE_INTEGER; | |
385 | case STRING: | |
386 | return TYPE_STRING; | |
1cbf1a19 FR |
387 | case LONG: |
388 | return TYPE_LONG; | |
b67a2540 AM |
389 | default: |
390 | /* Should not happen if the switch is fully covered */ | |
391 | throw new RuntimeException(); | |
392 | } | |
393 | } | |
a52fde77 | 394 | } |