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 | ||
aa353506 | 21 | import org.eclipse.jdt.annotation.NonNull; |
d69a6555 GB |
22 | import org.eclipse.tracecompass.internal.provisional.statesystem.core.statevalue.CustomStateValue; |
23 | import org.eclipse.tracecompass.internal.provisional.statesystem.core.statevalue.ISafeByteBufferReader; | |
24 | import org.eclipse.tracecompass.internal.provisional.statesystem.core.statevalue.ISafeByteBufferWriter; | |
25 | import org.eclipse.tracecompass.internal.provisional.statesystem.core.statevalue.SafeByteBufferFactory; | |
e894a508 AM |
26 | import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException; |
27 | import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval; | |
28 | import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue; | |
29 | import org.eclipse.tracecompass.statesystem.core.statevalue.TmfStateValue; | |
a52fde77 AM |
30 | |
31 | /** | |
32 | * The interval component, which will be contained in a node of the History | |
33 | * Tree. | |
98107b3f | 34 | * |
8d47cc34 | 35 | * @author Alexandre Montplaisir |
a52fde77 | 36 | */ |
8d47cc34 | 37 | public final class HTInterval implements ITmfStateInterval, Comparable<HTInterval> { |
a52fde77 | 38 | |
b67a2540 AM |
39 | private static final String errMsg = "Invalid interval data. Maybe your file is corrupt?"; //$NON-NLS-1$ |
40 | ||
41 | /* 'Byte' equivalent for state values types */ | |
42 | private static final byte TYPE_NULL = -1; | |
43 | private static final byte TYPE_INTEGER = 0; | |
44 | private static final byte TYPE_STRING = 1; | |
1cbf1a19 | 45 | private static final byte TYPE_LONG = 2; |
a3c22e8e | 46 | private static final byte TYPE_DOUBLE = 3; |
7ef85bff | 47 | private static final byte TYPE_CUSTOM = 20; |
b67a2540 | 48 | |
a52fde77 AM |
49 | private final long start; |
50 | private final long end; | |
51 | private final int attribute; | |
aa353506 | 52 | private final @NonNull TmfStateValue sv; |
a52fde77 | 53 | |
59d30d83 MK |
54 | /** Number of bytes used by this interval when it is written to disk */ |
55 | private final int fSizeOnDisk; | |
a52fde77 AM |
56 | |
57 | /** | |
58 | * Standard constructor | |
98107b3f | 59 | * |
a52fde77 | 60 | * @param intervalStart |
8d47cc34 | 61 | * Start time of the interval |
a52fde77 | 62 | * @param intervalEnd |
8d47cc34 | 63 | * End time of the interval |
a52fde77 | 64 | * @param attribute |
8d47cc34 AM |
65 | * Attribute (quark) to which the state represented by this |
66 | * interval belongs | |
a52fde77 | 67 | * @param value |
8d47cc34 | 68 | * State value represented by this interval |
a52fde77 | 69 | * @throws TimeRangeException |
8d47cc34 | 70 | * If the start time or end time are invalid |
a52fde77 | 71 | */ |
8d47cc34 | 72 | public HTInterval(long intervalStart, long intervalEnd, int attribute, |
aa353506 | 73 | @NonNull TmfStateValue value) throws TimeRangeException { |
a52fde77 | 74 | if (intervalStart > intervalEnd) { |
e13bd4cd | 75 | throw new TimeRangeException("Start:" + intervalStart + ", End:" + intervalEnd); //$NON-NLS-1$ //$NON-NLS-2$ |
a52fde77 AM |
76 | } |
77 | ||
78 | this.start = intervalStart; | |
79 | this.end = intervalEnd; | |
80 | this.attribute = attribute; | |
81 | this.sv = value; | |
59d30d83 MK |
82 | this.fSizeOnDisk = computeSizeOnDisk(sv); |
83 | } | |
84 | ||
85 | /** | |
86 | * Compute how much space (in bytes) an interval will take in its serialized | |
87 | * form on disk. This is dependent on its state value. | |
88 | */ | |
89 | private static int computeSizeOnDisk(ITmfStateValue sv) { | |
90 | /* | |
91 | * Minimum size is 2x long (start and end), 1x int (attribute) and 1x | |
92 | * byte (value type). | |
93 | */ | |
94 | int minSize = Long.BYTES + Long.BYTES + Integer.BYTES + Byte.BYTES; | |
95 | ||
96 | switch (sv.getType()) { | |
97 | case NULL: | |
98 | return minSize; | |
99 | case INTEGER: | |
100 | return (minSize + Integer.BYTES); | |
101 | case LONG: | |
102 | return (minSize + Long.BYTES); | |
103 | case DOUBLE: | |
104 | return (minSize + Double.BYTES); | |
105 | case STRING: | |
106 | /* | |
107 | * String's length + 2 (1 byte for size, 1 byte for \0 at the end | |
108 | */ | |
109 | return (minSize + sv.unboxStr().getBytes().length + 2); | |
7ef85bff | 110 | case CUSTOM: |
d69a6555 GB |
111 | /* Length of serialized value (short) + state value */ |
112 | return (minSize + Short.BYTES + ((CustomStateValue) sv).getSerializedSize()); | |
59d30d83 MK |
113 | default: |
114 | /* | |
115 | * It's very important that we know how to write the state value in | |
116 | * the file!! | |
117 | */ | |
118 | throw new IllegalStateException(); | |
119 | } | |
a52fde77 AM |
120 | } |
121 | ||
b4603a60 AM |
122 | /** |
123 | * "Faster" constructor for inner use only. When we build an interval when | |
124 | * reading it from disk (with {@link #readFrom}), we already know the size | |
125 | * of the strings entry, so there is no need to call | |
126 | * {@link #computeStringsEntrySize()} and do an extra copy. | |
b4603a60 AM |
127 | */ |
128 | private HTInterval(long intervalStart, long intervalEnd, int attribute, | |
aa353506 | 129 | @NonNull TmfStateValue value, int size) throws TimeRangeException { |
b4603a60 | 130 | if (intervalStart > intervalEnd) { |
e13bd4cd | 131 | throw new TimeRangeException("Start:" + intervalStart + ", End:" + intervalEnd); //$NON-NLS-1$ //$NON-NLS-2$ |
b4603a60 AM |
132 | } |
133 | ||
134 | this.start = intervalStart; | |
135 | this.end = intervalEnd; | |
136 | this.attribute = attribute; | |
137 | this.sv = value; | |
59d30d83 | 138 | this.fSizeOnDisk = size; |
b4603a60 AM |
139 | } |
140 | ||
a52fde77 | 141 | /** |
8d47cc34 | 142 | * Reader factory method. Builds the interval using an already-allocated |
a52fde77 | 143 | * ByteBuffer, which normally comes from a NIO FileChannel. |
98107b3f | 144 | * |
59d30d83 MK |
145 | * The interval is just a start, end, attribute and value, this is the |
146 | * layout of the HTInterval on disk | |
147 | * <ul> | |
148 | * <li>start (8 bytes)</li> | |
149 | * <li>end (8 bytes)</li> | |
150 | * <li>attribute (4 bytes)</li> | |
151 | * <li>sv type (1 byte)</li> | |
152 | * <li>sv ( 0 bytes for null, 4 for int , 8 for long and double, and the | |
153 | * length of the string +2 for strings (it's variable))</li> | |
154 | * </ul> | |
155 | * | |
a52fde77 AM |
156 | * @param buffer |
157 | * The ByteBuffer from which to read the information | |
8d47cc34 | 158 | * @return The interval object |
a52fde77 | 159 | * @throws IOException |
8d47cc34 | 160 | * If there was an error reading from the buffer |
a52fde77 | 161 | */ |
8d47cc34 | 162 | public static final HTInterval readFrom(ByteBuffer buffer) throws IOException { |
a52fde77 | 163 | TmfStateValue value; |
a52fde77 | 164 | |
59d30d83 | 165 | int posStart = buffer.position(); |
a52fde77 | 166 | /* Read the Data Section entry */ |
d69a6555 GB |
167 | long intervalStart = buffer.getLong(); |
168 | long intervalEnd = buffer.getLong(); | |
169 | int attribute = buffer.getInt(); | |
a52fde77 AM |
170 | |
171 | /* Read the 'type' of the value, then react accordingly */ | |
d69a6555 | 172 | byte valueType = buffer.get(); |
b67a2540 | 173 | switch (valueType) { |
a52fde77 | 174 | |
b67a2540 AM |
175 | case TYPE_NULL: |
176 | value = TmfStateValue.nullValue(); | |
177 | break; | |
a52fde77 | 178 | |
b67a2540 | 179 | case TYPE_INTEGER: |
59d30d83 | 180 | value = TmfStateValue.newValueInt(buffer.getInt()); |
b67a2540 AM |
181 | break; |
182 | ||
d69a6555 | 183 | case TYPE_STRING: { |
a52fde77 | 184 | /* the first byte = the size to read */ |
59d30d83 | 185 | int valueSize = buffer.get(); |
a52fde77 | 186 | |
d69a6555 | 187 | byte[] array = new byte[valueSize]; |
a52fde77 AM |
188 | buffer.get(array); |
189 | value = TmfStateValue.newValueString(new String(array)); | |
190 | ||
191 | /* Confirm the 0'ed byte at the end */ | |
59d30d83 | 192 | byte res = buffer.get(); |
a52fde77 | 193 | if (res != 0) { |
b67a2540 | 194 | throw new IOException(errMsg); |
a52fde77 | 195 | } |
1cbf1a19 | 196 | break; |
d69a6555 | 197 | } |
1cbf1a19 FR |
198 | |
199 | case TYPE_LONG: | |
200 | /* Go read the matching entry in the Strings section of the block */ | |
1cbf1a19 | 201 | value = TmfStateValue.newValueLong(buffer.getLong()); |
b67a2540 | 202 | break; |
a3c22e8e AM |
203 | |
204 | case TYPE_DOUBLE: | |
205 | /* Go read the matching entry in the Strings section of the block */ | |
a3c22e8e | 206 | value = TmfStateValue.newValueDouble(buffer.getDouble()); |
a3c22e8e AM |
207 | break; |
208 | ||
d69a6555 GB |
209 | case TYPE_CUSTOM: { |
210 | short valueSize = buffer.getShort(); | |
211 | ISafeByteBufferReader safeBuffer = SafeByteBufferFactory.wrapReader(buffer, valueSize); | |
212 | value = CustomStateValue.readSerializedValue(safeBuffer); | |
213 | break; | |
214 | } | |
b67a2540 AM |
215 | default: |
216 | /* Unknown data, better to not make anything up... */ | |
217 | throw new IOException(errMsg); | |
a52fde77 AM |
218 | } |
219 | ||
220 | try { | |
d69a6555 | 221 | return new HTInterval(intervalStart, intervalEnd, attribute, value, buffer.position() - posStart); |
a52fde77 | 222 | } catch (TimeRangeException e) { |
b67a2540 | 223 | throw new IOException(errMsg); |
a52fde77 | 224 | } |
a52fde77 AM |
225 | } |
226 | ||
227 | /** | |
228 | * Antagonist of the previous constructor, write the Data entry | |
229 | * corresponding to this interval in a ByteBuffer (mapped to a block in the | |
230 | * history-file, hopefully) | |
98107b3f | 231 | * |
59d30d83 MK |
232 | * The interval is just a start, end, attribute and value, this is the |
233 | * layout of the HTInterval on disk | |
234 | * <ul> | |
235 | * <li>start (8 bytes)</li> | |
236 | * <li>end (8 bytes)</li> | |
237 | * <li>attribute (4 bytes)</li> | |
238 | * <li>sv type (1 byte)</li> | |
239 | * <li>sv ( 0 bytes for null, 4 for int , 8 for long and double, and the | |
240 | * length of the string +2 for strings (it's variable))</li> | |
241 | * </ul> | |
242 | * | |
a52fde77 AM |
243 | * @param buffer |
244 | * The already-allocated ByteBuffer corresponding to a SHT Node | |
a52fde77 | 245 | */ |
59d30d83 MK |
246 | public void writeInterval(ByteBuffer buffer) { |
247 | final byte byteFromType = getByteFromType(sv.getType()); | |
248 | ||
a52fde77 AM |
249 | buffer.putLong(start); |
250 | buffer.putLong(end); | |
251 | buffer.putInt(attribute); | |
59d30d83 | 252 | buffer.put(byteFromType); |
a52fde77 | 253 | |
59d30d83 | 254 | switch (byteFromType) { |
1cbf1a19 | 255 | case TYPE_NULL: |
59d30d83 | 256 | break; |
1cbf1a19 | 257 | case TYPE_INTEGER: |
59d30d83 | 258 | buffer.putInt(sv.unboxInt()); |
9177eb74 | 259 | break; |
a52fde77 | 260 | |
d69a6555 | 261 | case TYPE_STRING: { |
59d30d83 MK |
262 | String string = sv.unboxStr(); |
263 | byte[] strArray = string.getBytes(); | |
1cbf1a19 FR |
264 | |
265 | /* | |
59d30d83 MK |
266 | * Write the Strings entry (1st byte = size, then the bytes, then |
267 | * the 0). We have checked the string length at the constructor. | |
1cbf1a19 | 268 | */ |
59d30d83 MK |
269 | buffer.put((byte) strArray.length); |
270 | buffer.put(strArray); | |
1cbf1a19 | 271 | buffer.put((byte) 0); |
9177eb74 | 272 | break; |
d69a6555 | 273 | } |
1cbf1a19 FR |
274 | |
275 | case TYPE_LONG: | |
59d30d83 | 276 | buffer.putLong(sv.unboxLong()); |
9177eb74 | 277 | break; |
1cbf1a19 | 278 | |
a3c22e8e | 279 | case TYPE_DOUBLE: |
59d30d83 | 280 | buffer.putDouble(sv.unboxDouble()); |
a3c22e8e AM |
281 | break; |
282 | ||
d69a6555 GB |
283 | case TYPE_CUSTOM: { |
284 | int size = ((CustomStateValue) sv).getSerializedSize(); | |
285 | buffer.putShort((short) size); | |
286 | ISafeByteBufferWriter safeBuffer = SafeByteBufferFactory.wrapWriter(buffer, size); | |
287 | ((CustomStateValue) sv).serialize(safeBuffer); | |
288 | break; | |
289 | } | |
290 | ||
1cbf1a19 | 291 | default: |
9177eb74 | 292 | break; |
a52fde77 | 293 | } |
a52fde77 AM |
294 | } |
295 | ||
296 | @Override | |
297 | public long getStartTime() { | |
298 | return start; | |
299 | } | |
300 | ||
301 | @Override | |
302 | public long getEndTime() { | |
303 | return end; | |
304 | } | |
305 | ||
306 | @Override | |
307 | public int getAttribute() { | |
308 | return attribute; | |
309 | } | |
310 | ||
311 | @Override | |
312 | public ITmfStateValue getStateValue() { | |
313 | return sv; | |
314 | } | |
315 | ||
316 | @Override | |
317 | public boolean intersects(long timestamp) { | |
318 | if (start <= timestamp) { | |
319 | if (end >= timestamp) { | |
320 | return true; | |
321 | } | |
322 | } | |
323 | return false; | |
324 | } | |
325 | ||
a52fde77 AM |
326 | /** |
327 | * Total serialized size of this interval | |
98107b3f | 328 | * |
8d47cc34 | 329 | * @return The interval size |
a52fde77 | 330 | */ |
59d30d83 MK |
331 | public int getSizeOnDisk() { |
332 | return fSizeOnDisk; | |
a52fde77 AM |
333 | } |
334 | ||
335 | /** | |
336 | * Compare the END TIMES of different intervals. This is used to sort the | |
337 | * intervals when we close down a node. | |
338 | */ | |
339 | @Override | |
340 | public int compareTo(HTInterval other) { | |
341 | if (this.end < other.end) { | |
342 | return -1; | |
343 | } else if (this.end > other.end) { | |
344 | return 1; | |
345 | } else { | |
346 | return 0; | |
347 | } | |
348 | } | |
1b558482 | 349 | |
ab604305 AM |
350 | @Override |
351 | public boolean equals(Object other) { | |
cb42195c AM |
352 | if (other instanceof HTInterval && |
353 | this.compareTo((HTInterval) other) == 0) { | |
354 | return true; | |
ab604305 AM |
355 | } |
356 | return false; | |
357 | } | |
358 | ||
359 | @Override | |
360 | public int hashCode() { | |
361 | return super.hashCode(); | |
362 | } | |
1b558482 AM |
363 | |
364 | @Override | |
365 | public String toString() { | |
366 | /* Only for debug, should not be externalized */ | |
98107b3f AM |
367 | StringBuilder sb = new StringBuilder(); |
368 | sb.append('['); | |
369 | sb.append(start); | |
370 | sb.append(", "); //$NON-NLS-1$ | |
371 | sb.append(end); | |
372 | sb.append(']'); | |
373 | ||
374 | sb.append(", attribute = "); //$NON-NLS-1$ | |
375 | sb.append(attribute); | |
376 | ||
377 | sb.append(", value = "); //$NON-NLS-1$ | |
378 | sb.append(sv.toString()); | |
379 | ||
380 | return sb.toString(); | |
1b558482 | 381 | } |
b67a2540 AM |
382 | |
383 | /** | |
59d30d83 MK |
384 | * Here we determine how state values "types" are written in the 8-bit field |
385 | * that indicates the value type in the file. | |
b67a2540 AM |
386 | */ |
387 | private static byte getByteFromType(ITmfStateValue.Type type) { | |
59d30d83 | 388 | switch (type) { |
b67a2540 AM |
389 | case NULL: |
390 | return TYPE_NULL; | |
391 | case INTEGER: | |
392 | return TYPE_INTEGER; | |
393 | case STRING: | |
394 | return TYPE_STRING; | |
1cbf1a19 FR |
395 | case LONG: |
396 | return TYPE_LONG; | |
a3c22e8e AM |
397 | case DOUBLE: |
398 | return TYPE_DOUBLE; | |
7ef85bff GB |
399 | case CUSTOM: |
400 | return TYPE_CUSTOM; | |
b67a2540 AM |
401 | default: |
402 | /* Should not happen if the switch is fully covered */ | |
cb42195c | 403 | throw new IllegalStateException(); |
b67a2540 AM |
404 | } |
405 | } | |
a52fde77 | 406 | } |