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