Commit | Line | Data |
---|---|---|
a52fde77 | 1 | /******************************************************************************* |
2a966f6f | 2 | * Copyright (c) 2010, 2015 Ericsson, École Polytechnique de Montréal, and others |
6f4e8ec0 | 3 | * |
a52fde77 AM |
4 | * All rights reserved. This program and the accompanying materials are |
5 | * made available under the terms of the Eclipse Public License v1.0 which | |
6 | * accompanies this distribution, and is available at | |
7 | * http://www.eclipse.org/legal/epl-v10.html | |
6f4e8ec0 | 8 | * |
bb7f92ce FW |
9 | * Contributors: |
10 | * Alexandre Montplaisir - Initial API and implementation | |
11 | * Florian Wininger - Add Extension and Leaf Node | |
2a966f6f | 12 | * Patrick Tasse - Keep interval list sorted on insert |
a52fde77 AM |
13 | *******************************************************************************/ |
14 | ||
e894a508 | 15 | package org.eclipse.tracecompass.internal.statesystem.core.backend.historytree; |
a52fde77 AM |
16 | |
17 | import java.io.IOException; | |
18 | import java.io.PrintWriter; | |
19 | import java.nio.ByteBuffer; | |
20 | import java.nio.ByteOrder; | |
21 | import java.nio.channels.FileChannel; | |
22 | import java.util.ArrayList; | |
23 | import java.util.Collections; | |
24 | import java.util.List; | |
62197b87 | 25 | import java.util.concurrent.locks.ReentrantReadWriteLock; |
a52fde77 | 26 | |
e894a508 AM |
27 | import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException; |
28 | import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval; | |
29 | import org.eclipse.tracecompass.statesystem.core.statevalue.TmfStateValue; | |
a52fde77 AM |
30 | |
31 | /** | |
32 | * The base class for all the types of nodes that go in the History Tree. | |
6f4e8ec0 | 33 | * |
ffd0aa67 | 34 | * @author Alexandre Montplaisir |
a52fde77 | 35 | */ |
8d47cc34 | 36 | public abstract class HTNode { |
a52fde77 | 37 | |
bb7f92ce FW |
38 | // ------------------------------------------------------------------------ |
39 | // Class fields | |
40 | // ------------------------------------------------------------------------ | |
41 | ||
42 | /** | |
43 | * The type of node | |
44 | */ | |
45 | public static enum NodeType { | |
46 | /** | |
47 | * Core node, which is a "front" node, at any level of the tree except | |
48 | * the bottom-most one. It has children, and may have extensions. | |
49 | */ | |
50 | CORE, | |
51 | /** | |
52 | * Leaf node, which is a node at the last bottom level of the tree. It | |
53 | * cannot have any children or extensions. | |
54 | */ | |
55 | LEAF; | |
56 | ||
57 | /** | |
58 | * Determine a node type by reading a serialized byte. | |
59 | * | |
60 | * @param rep | |
61 | * The byte representation of the node type | |
62 | * @return The corresponding NodeType | |
63 | * @throws IOException | |
64 | * If the NodeType is unrecognized | |
65 | */ | |
66 | public static NodeType fromByte(byte rep) throws IOException { | |
67 | switch (rep) { | |
68 | case 1: | |
69 | return CORE; | |
70 | case 2: | |
71 | return LEAF; | |
72 | default: | |
73 | throw new IOException(); | |
74 | } | |
75 | } | |
76 | ||
77 | /** | |
78 | * Get the byte representation of this node type. It can then be read | |
79 | * with {@link #fromByte}. | |
80 | * | |
81 | * @return The byte matching this node type | |
82 | */ | |
83 | public byte toByte() { | |
84 | switch (this) { | |
85 | case CORE: | |
86 | return 1; | |
87 | case LEAF: | |
88 | return 2; | |
89 | default: | |
90 | throw new IllegalStateException(); | |
91 | } | |
92 | } | |
93 | } | |
94 | ||
95 | // ------------------------------------------------------------------------ | |
96 | // Attributes | |
97 | // ------------------------------------------------------------------------ | |
98 | ||
ffd0aa67 EB |
99 | /* Configuration of the History Tree to which belongs this node */ |
100 | private final HTConfig config; | |
a52fde77 AM |
101 | |
102 | /* Time range of this node */ | |
103 | private final long nodeStart; | |
104 | private long nodeEnd; | |
105 | ||
106 | /* Sequence number = position in the node section of the file */ | |
107 | private final int sequenceNumber; | |
108 | private int parentSequenceNumber; /* = -1 if this node is the root node */ | |
109 | ||
110 | /* Where the Strings section begins (from the start of the node */ | |
111 | private int stringSectionOffset; | |
112 | ||
b0136ad6 FW |
113 | /* Sum of bytes of all intervals in the node */ |
114 | private int sizeOfIntervalSection; | |
115 | ||
045badfe AM |
116 | /* True if this node was read from disk (meaning its end time is now fixed) */ |
117 | private volatile boolean isOnDisk; | |
a52fde77 AM |
118 | |
119 | /* Vector containing all the intervals contained in this node */ | |
cb42195c | 120 | private final List<HTInterval> intervals; |
a52fde77 | 121 | |
62197b87 AM |
122 | /* Lock used to protect the accesses to intervals, nodeEnd and such */ |
123 | private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(false); | |
124 | ||
8d47cc34 AM |
125 | /** |
126 | * Constructor | |
127 | * | |
128 | * @param config | |
129 | * Configuration of the History Tree | |
130 | * @param seqNumber | |
131 | * The (unique) sequence number assigned to this particular node | |
132 | * @param parentSeqNumber | |
133 | * The sequence number of this node's parent node | |
134 | * @param start | |
135 | * The earliest timestamp stored in this node | |
136 | */ | |
137 | protected HTNode(HTConfig config, int seqNumber, int parentSeqNumber, long start) { | |
ffd0aa67 | 138 | this.config = config; |
a52fde77 AM |
139 | this.nodeStart = start; |
140 | this.sequenceNumber = seqNumber; | |
141 | this.parentSequenceNumber = parentSeqNumber; | |
142 | ||
ffd0aa67 | 143 | this.stringSectionOffset = config.getBlockSize(); |
b0136ad6 | 144 | this.sizeOfIntervalSection = 0; |
045badfe | 145 | this.isOnDisk = false; |
a4524c1b | 146 | this.intervals = new ArrayList<>(); |
a52fde77 AM |
147 | } |
148 | ||
149 | /** | |
8d47cc34 AM |
150 | * Reader factory method. Build a Node object (of the right type) by reading |
151 | * a block in the file. | |
6f4e8ec0 | 152 | * |
ffd0aa67 EB |
153 | * @param config |
154 | * Configuration of the History Tree | |
a52fde77 AM |
155 | * @param fc |
156 | * FileChannel to the history file, ALREADY SEEKED at the start | |
157 | * of the node. | |
8d47cc34 | 158 | * @return The node object |
a52fde77 | 159 | * @throws IOException |
8d47cc34 | 160 | * If there was an error reading from the file channel |
a52fde77 | 161 | */ |
8d47cc34 | 162 | public static final HTNode readNode(HTConfig config, FileChannel fc) |
a52fde77 AM |
163 | throws IOException { |
164 | HTNode newNode = null; | |
165 | int res, i; | |
166 | ||
ffd0aa67 | 167 | ByteBuffer buffer = ByteBuffer.allocate(config.getBlockSize()); |
a52fde77 AM |
168 | buffer.order(ByteOrder.LITTLE_ENDIAN); |
169 | buffer.clear(); | |
170 | res = fc.read(buffer); | |
ffd0aa67 | 171 | assert (res == config.getBlockSize()); |
a52fde77 AM |
172 | buffer.flip(); |
173 | ||
174 | /* Read the common header part */ | |
bb7f92ce FW |
175 | byte typeByte = buffer.get(); |
176 | NodeType type = NodeType.fromByte(typeByte); | |
a52fde77 AM |
177 | long start = buffer.getLong(); |
178 | long end = buffer.getLong(); | |
179 | int seqNb = buffer.getInt(); | |
180 | int parentSeqNb = buffer.getInt(); | |
181 | int intervalCount = buffer.getInt(); | |
182 | int stringSectionOffset = buffer.getInt(); | |
045badfe | 183 | buffer.get(); // TODO Used to be "isDone", to be removed from the header |
a52fde77 AM |
184 | |
185 | /* Now the rest of the header depends on the node type */ | |
186 | switch (type) { | |
bb7f92ce | 187 | case CORE: |
a52fde77 | 188 | /* Core nodes */ |
ffd0aa67 | 189 | newNode = new CoreNode(config, seqNb, parentSeqNb, start); |
a52fde77 AM |
190 | newNode.readSpecificHeader(buffer); |
191 | break; | |
192 | ||
bb7f92ce FW |
193 | case LEAF: |
194 | /* Leaf nodes */ | |
195 | newNode = new LeafNode(config, seqNb, parentSeqNb, start); | |
196 | newNode.readSpecificHeader(buffer); | |
197 | break; | |
a52fde77 AM |
198 | |
199 | default: | |
200 | /* Unrecognized node type */ | |
201 | throw new IOException(); | |
202 | } | |
203 | ||
204 | /* | |
205 | * At this point, we should be done reading the header and 'buffer' | |
206 | * should only have the intervals left | |
207 | */ | |
208 | for (i = 0; i < intervalCount; i++) { | |
0ce45cd4 GB |
209 | HTInterval interval = HTInterval.readFrom(buffer); |
210 | newNode.intervals.add(interval); | |
211 | newNode.sizeOfIntervalSection += interval.getIntervalSize(); | |
a52fde77 AM |
212 | } |
213 | ||
214 | /* Assign the node's other information we have read previously */ | |
215 | newNode.nodeEnd = end; | |
216 | newNode.stringSectionOffset = stringSectionOffset; | |
045badfe | 217 | newNode.isOnDisk = true; |
a52fde77 AM |
218 | |
219 | return newNode; | |
220 | } | |
221 | ||
8d47cc34 AM |
222 | /** |
223 | * Write this node to the given file channel. | |
224 | * | |
225 | * @param fc | |
226 | * The file channel to write to (should be sought to be correct | |
227 | * position) | |
228 | * @throws IOException | |
229 | * If there was an error writing | |
230 | */ | |
231 | public final void writeSelf(FileChannel fc) throws IOException { | |
a52fde77 | 232 | /* |
62197b87 AM |
233 | * Yes, we are taking the *read* lock here, because we are reading the |
234 | * information in the node to write it to disk. | |
a52fde77 | 235 | */ |
62197b87 AM |
236 | rwl.readLock().lock(); |
237 | try { | |
238 | final int blockSize = config.getBlockSize(); | |
239 | int curStringsEntryEndPos = blockSize; | |
240 | ||
241 | ByteBuffer buffer = ByteBuffer.allocate(blockSize); | |
242 | buffer.order(ByteOrder.LITTLE_ENDIAN); | |
243 | buffer.clear(); | |
244 | ||
245 | /* Write the common header part */ | |
bb7f92ce | 246 | buffer.put(this.getNodeType().toByte()); |
62197b87 AM |
247 | buffer.putLong(nodeStart); |
248 | buffer.putLong(nodeEnd); | |
249 | buffer.putInt(sequenceNumber); | |
250 | buffer.putInt(parentSequenceNumber); | |
251 | buffer.putInt(intervals.size()); | |
252 | buffer.putInt(stringSectionOffset); | |
253 | buffer.put((byte) 1); // TODO Used to be "isDone", to be removed from header | |
254 | ||
255 | /* Now call the inner method to write the specific header part */ | |
256 | this.writeSpecificHeader(buffer); | |
257 | ||
258 | /* Back to us, we write the intervals */ | |
259 | for (HTInterval interval : intervals) { | |
260 | int size = interval.writeInterval(buffer, curStringsEntryEndPos); | |
261 | curStringsEntryEndPos -= size; | |
262 | } | |
a52fde77 | 263 | |
62197b87 AM |
264 | /* |
265 | * Write padding between the end of the Data section and the start | |
266 | * of the Strings section (needed to fill the node in case there is | |
267 | * no Strings section) | |
268 | */ | |
269 | while (buffer.position() < stringSectionOffset) { | |
270 | buffer.put((byte) 0); | |
271 | } | |
a52fde77 | 272 | |
62197b87 AM |
273 | /* |
274 | * If the offsets were right, the size of the Strings section should | |
275 | * be == to the expected size | |
276 | */ | |
277 | assert (curStringsEntryEndPos == stringSectionOffset); | |
a52fde77 | 278 | |
62197b87 | 279 | /* Finally, write everything in the Buffer to disk */ |
a52fde77 | 280 | |
62197b87 AM |
281 | // if we don't do this, flip() will lose what's after. |
282 | buffer.position(blockSize); | |
283 | ||
284 | buffer.flip(); | |
285 | int res = fc.write(buffer); | |
286 | assert (res == blockSize); | |
287 | ||
288 | } finally { | |
289 | rwl.readLock().unlock(); | |
290 | } | |
045badfe | 291 | isOnDisk = true; |
cb42195c AM |
292 | } |
293 | ||
294 | // ------------------------------------------------------------------------ | |
295 | // Accessors | |
296 | // ------------------------------------------------------------------------ | |
297 | ||
8d47cc34 AM |
298 | /** |
299 | * Retrieve the history tree configuration used for this node. | |
300 | * | |
301 | * @return The history tree config | |
302 | */ | |
303 | protected HTConfig getConfig() { | |
ffd0aa67 | 304 | return config; |
a52fde77 AM |
305 | } |
306 | ||
8d47cc34 AM |
307 | /** |
308 | * Get the start time of this node. | |
309 | * | |
310 | * @return The start time of this node | |
311 | */ | |
312 | public long getNodeStart() { | |
a52fde77 AM |
313 | return nodeStart; |
314 | } | |
315 | ||
8d47cc34 AM |
316 | /** |
317 | * Get the end time of this node. | |
318 | * | |
bb7f92ce | 319 | * @return The end time of this node |
8d47cc34 AM |
320 | */ |
321 | public long getNodeEnd() { | |
045badfe | 322 | if (this.isOnDisk) { |
a52fde77 AM |
323 | return nodeEnd; |
324 | } | |
325 | return 0; | |
326 | } | |
327 | ||
8d47cc34 AM |
328 | /** |
329 | * Get the sequence number of this node. | |
330 | * | |
331 | * @return The sequence number of this node | |
332 | */ | |
333 | public int getSequenceNumber() { | |
a52fde77 AM |
334 | return sequenceNumber; |
335 | } | |
336 | ||
8d47cc34 AM |
337 | /** |
338 | * Get the sequence number of this node's parent. | |
339 | * | |
340 | * @return The parent sequence number | |
341 | */ | |
342 | public int getParentSequenceNumber() { | |
a52fde77 AM |
343 | return parentSequenceNumber; |
344 | } | |
345 | ||
346 | /** | |
347 | * Change this node's parent. Used when we create a new root node for | |
348 | * example. | |
8d47cc34 AM |
349 | * |
350 | * @param newParent | |
351 | * The sequence number of the node that is the new parent | |
a52fde77 | 352 | */ |
8d47cc34 | 353 | public void setParentSequenceNumber(int newParent) { |
a52fde77 AM |
354 | parentSequenceNumber = newParent; |
355 | } | |
356 | ||
8d47cc34 AM |
357 | /** |
358 | * Return if this node is "done" (full and written to disk). | |
359 | * | |
360 | * @return If this node is done or not | |
361 | */ | |
045badfe AM |
362 | public boolean isOnDisk() { |
363 | return isOnDisk; | |
a52fde77 AM |
364 | } |
365 | ||
366 | /** | |
367 | * Add an interval to this node | |
6f4e8ec0 | 368 | * |
a52fde77 | 369 | * @param newInterval |
8d47cc34 | 370 | * Interval to add to this node |
a52fde77 | 371 | */ |
8d47cc34 | 372 | public void addInterval(HTInterval newInterval) { |
62197b87 AM |
373 | rwl.writeLock().lock(); |
374 | try { | |
375 | /* Just in case, should be checked before even calling this function */ | |
376 | assert (newInterval.getIntervalSize() <= this.getNodeFreeSpace()); | |
a52fde77 | 377 | |
2a966f6f PT |
378 | /* Find the insert position to keep the list sorted */ |
379 | int index = intervals.size(); | |
380 | while (index > 0 && newInterval.compareTo(intervals.get(index - 1)) < 0) { | |
381 | index--; | |
382 | } | |
383 | ||
384 | intervals.add(index, newInterval); | |
b0136ad6 | 385 | sizeOfIntervalSection += newInterval.getIntervalSize(); |
a52fde77 | 386 | |
62197b87 AM |
387 | /* Update the in-node offset "pointer" */ |
388 | stringSectionOffset -= (newInterval.getStringsEntrySize()); | |
389 | } finally { | |
390 | rwl.writeLock().unlock(); | |
391 | } | |
a52fde77 AM |
392 | } |
393 | ||
394 | /** | |
395 | * We've received word from the containerTree that newest nodes now exist to | |
396 | * our right. (Puts isDone = true and sets the endtime) | |
6f4e8ec0 | 397 | * |
a52fde77 AM |
398 | * @param endtime |
399 | * The nodeEnd time that the node will have | |
a52fde77 | 400 | */ |
8d47cc34 | 401 | public void closeThisNode(long endtime) { |
62197b87 AM |
402 | rwl.writeLock().lock(); |
403 | try { | |
404 | assert (endtime >= this.nodeStart); | |
405 | ||
6642afb4 | 406 | if (!intervals.isEmpty()) { |
62197b87 AM |
407 | /* |
408 | * Make sure there are no intervals in this node with their | |
409 | * EndTime > the one requested. Only need to check the last one | |
2a966f6f | 410 | * since they are sorted |
62197b87 AM |
411 | */ |
412 | assert (endtime >= intervals.get(intervals.size() - 1).getEndTime()); | |
413 | } | |
a52fde77 | 414 | |
62197b87 AM |
415 | this.nodeEnd = endtime; |
416 | } finally { | |
417 | rwl.writeLock().unlock(); | |
a52fde77 | 418 | } |
a52fde77 AM |
419 | } |
420 | ||
421 | /** | |
422 | * The method to fill up the stateInfo (passed on from the Current State | |
423 | * Tree when it does a query on the SHT). We'll replace the data in that | |
424 | * vector with whatever relevant we can find from this node | |
6f4e8ec0 | 425 | * |
a52fde77 AM |
426 | * @param stateInfo |
427 | * The same stateInfo that comes from SHT's doQuery() | |
428 | * @param t | |
429 | * The timestamp for which the query is for. Only return | |
430 | * intervals that intersect t. | |
431 | * @throws TimeRangeException | |
8d47cc34 | 432 | * If 't' is invalid |
a52fde77 | 433 | */ |
8d47cc34 | 434 | public void writeInfoFromNode(List<ITmfStateInterval> stateInfo, long t) |
a52fde77 | 435 | throws TimeRangeException { |
62197b87 AM |
436 | /* This is from a state system query, we are "reading" this node */ |
437 | rwl.readLock().lock(); | |
438 | try { | |
6642afb4 | 439 | for (int i = getStartIndexFor(t); i < intervals.size(); i++) { |
62197b87 AM |
440 | /* |
441 | * Now we only have to compare the Start times, since we now the | |
1d8028cd AM |
442 | * End times necessarily fit. |
443 | * | |
444 | * Second condition is to ignore new attributes that might have | |
445 | * been created after stateInfo was instantiated (they would be | |
446 | * null anyway). | |
62197b87 | 447 | */ |
1d8028cd AM |
448 | ITmfStateInterval interval = intervals.get(i); |
449 | if (interval.getStartTime() <= t && | |
450 | interval.getAttribute() < stateInfo.size()) { | |
451 | stateInfo.set(interval.getAttribute(), interval); | |
62197b87 | 452 | } |
a52fde77 | 453 | } |
62197b87 AM |
454 | } finally { |
455 | rwl.readLock().unlock(); | |
a52fde77 | 456 | } |
a52fde77 AM |
457 | } |
458 | ||
459 | /** | |
460 | * Get a single Interval from the information in this node If the | |
461 | * key/timestamp pair cannot be found, we return null. | |
6f4e8ec0 | 462 | * |
a52fde77 | 463 | * @param key |
8d47cc34 | 464 | * The attribute quark to look for |
a52fde77 | 465 | * @param t |
8d47cc34 | 466 | * The timestamp |
a52fde77 AM |
467 | * @return The Interval containing the information we want, or null if it |
468 | * wasn't found | |
bb7f92ce FW |
469 | * @throws TimeRangeException |
470 | * If 't' is invalid | |
a52fde77 | 471 | */ |
8d47cc34 | 472 | public HTInterval getRelevantInterval(int key, long t) throws TimeRangeException { |
62197b87 AM |
473 | rwl.readLock().lock(); |
474 | try { | |
6642afb4 | 475 | for (int i = getStartIndexFor(t); i < intervals.size(); i++) { |
62197b87 AM |
476 | HTInterval curInterval = intervals.get(i); |
477 | if (curInterval.getAttribute() == key | |
478 | && curInterval.getStartTime() <= t | |
479 | && curInterval.getEndTime() >= t) { | |
480 | return curInterval; | |
481 | } | |
a52fde77 | 482 | } |
6642afb4 | 483 | |
62197b87 AM |
484 | /* We didn't find the relevant information in this node */ |
485 | return null; | |
486 | ||
487 | } finally { | |
488 | rwl.readLock().unlock(); | |
a52fde77 | 489 | } |
a52fde77 AM |
490 | } |
491 | ||
492 | private int getStartIndexFor(long t) throws TimeRangeException { | |
62197b87 | 493 | /* Should only be called by methods with the readLock taken */ |
6642afb4 FW |
494 | |
495 | if (intervals.isEmpty()) { | |
496 | return 0; | |
497 | } | |
a52fde77 AM |
498 | /* |
499 | * Since the intervals are sorted by end time, we can skip all the ones | |
500 | * at the beginning whose end times are smaller than 't'. Java does | |
501 | * provides a .binarySearch method, but its API is quite weird... | |
502 | */ | |
62197b87 AM |
503 | HTInterval dummy = new HTInterval(0, t, 0, TmfStateValue.nullValue()); |
504 | int index = Collections.binarySearch(intervals, dummy); | |
a52fde77 AM |
505 | |
506 | if (index < 0) { | |
507 | /* | |
508 | * .binarySearch returns a negative number if the exact value was | |
509 | * not found. Here we just want to know where to start searching, we | |
510 | * don't care if the value is exact or not. | |
511 | */ | |
512 | index = -index - 1; | |
513 | ||
514 | } | |
515 | ||
516 | /* Sometimes binarySearch yields weird stuff... */ | |
517 | if (index < 0) { | |
518 | index = 0; | |
519 | } | |
520 | if (index >= intervals.size()) { | |
521 | index = intervals.size() - 1; | |
522 | } | |
523 | ||
524 | /* | |
525 | * Another API quirkiness, the returned index is the one of the *last* | |
526 | * element of a series of equal endtimes, which happens sometimes. We | |
527 | * want the *first* element of such a series, to read through them | |
528 | * again. | |
529 | */ | |
530 | while (index > 0 | |
531 | && intervals.get(index - 1).compareTo(intervals.get(index)) == 0) { | |
532 | index--; | |
533 | } | |
a52fde77 AM |
534 | |
535 | return index; | |
536 | } | |
537 | ||
62197b87 AM |
538 | /** |
539 | * <pre> | |
540 | * 1 - byte (type) | |
541 | * 16 - 2x long (start time, end time) | |
542 | * 16 - 4x int (seq number, parent seq number, intervalcount, | |
543 | * strings section pos.) | |
544 | * 1 - byte (done or not) | |
545 | * </pre> | |
546 | */ | |
547 | private static final int COMMON_HEADER_SIZE = 34; | |
548 | ||
549 | /** | |
550 | * Return the total header size of this node (will depend on the node type). | |
551 | * | |
552 | * @return The total header size | |
553 | */ | |
554 | public final int getTotalHeaderSize() { | |
555 | return COMMON_HEADER_SIZE + getSpecificHeaderSize(); | |
556 | } | |
557 | ||
a52fde77 AM |
558 | /** |
559 | * @return The offset, within the node, where the Data section ends | |
560 | */ | |
561 | private int getDataSectionEndOffset() { | |
b0136ad6 | 562 | return this.getTotalHeaderSize() + sizeOfIntervalSection; |
a52fde77 AM |
563 | } |
564 | ||
565 | /** | |
566 | * Returns the free space in the node, which is simply put, the | |
567 | * stringSectionOffset - dataSectionOffset | |
8d47cc34 AM |
568 | * |
569 | * @return The amount of free space in the node (in bytes) | |
a52fde77 | 570 | */ |
8d47cc34 | 571 | public int getNodeFreeSpace() { |
62197b87 AM |
572 | rwl.readLock().lock(); |
573 | int ret = stringSectionOffset - this.getDataSectionEndOffset(); | |
574 | rwl.readLock().unlock(); | |
575 | ||
576 | return ret; | |
a52fde77 AM |
577 | } |
578 | ||
579 | /** | |
8d47cc34 | 580 | * Returns the current space utilization of this node, as a percentage. |
a52fde77 | 581 | * (used space / total usable space, which excludes the header) |
8d47cc34 AM |
582 | * |
583 | * @return The percentage (value between 0 and 100) of space utilization in | |
584 | * in this node. | |
a52fde77 | 585 | */ |
8d47cc34 | 586 | public long getNodeUsagePercent() { |
62197b87 AM |
587 | rwl.readLock().lock(); |
588 | try { | |
589 | final int blockSize = config.getBlockSize(); | |
590 | float freePercent = (float) this.getNodeFreeSpace() | |
591 | / (float) (blockSize - this.getTotalHeaderSize()) | |
592 | * 100F; | |
593 | return (long) (100L - freePercent); | |
594 | ||
595 | } finally { | |
596 | rwl.readLock().unlock(); | |
597 | } | |
a52fde77 AM |
598 | } |
599 | ||
a52fde77 AM |
600 | /** |
601 | * @name Debugging functions | |
602 | */ | |
603 | ||
604 | @SuppressWarnings("nls") | |
605 | @Override | |
606 | public String toString() { | |
607 | /* Only used for debugging, shouldn't be externalized */ | |
608 | StringBuffer buf = new StringBuffer("Node #" + sequenceNumber + ", "); | |
609 | buf.append(this.toStringSpecific()); | |
8d47cc34 | 610 | buf.append(intervals.size() + " intervals (" + this.getNodeUsagePercent() |
a52fde77 AM |
611 | + "% used), "); |
612 | ||
613 | buf.append("[" + this.nodeStart + " - "); | |
045badfe | 614 | if (this.isOnDisk) { |
a52fde77 AM |
615 | buf = buf.append("" + this.nodeEnd + "]"); |
616 | } else { | |
617 | buf = buf.append("...]"); | |
618 | } | |
619 | return buf.toString(); | |
620 | } | |
621 | ||
622 | /** | |
623 | * Debugging function that prints out the contents of this node | |
6f4e8ec0 | 624 | * |
a52fde77 AM |
625 | * @param writer |
626 | * PrintWriter in which we will print the debug output | |
627 | */ | |
628 | @SuppressWarnings("nls") | |
8d47cc34 | 629 | public void debugPrintIntervals(PrintWriter writer) { |
a52fde77 AM |
630 | /* Only used for debugging, shouldn't be externalized */ |
631 | writer.println("Node #" + sequenceNumber + ":"); | |
632 | ||
633 | /* Array of children */ | |
bb7f92ce | 634 | if (this.getNodeType() == NodeType.CORE) { /* Only Core Nodes can have children */ |
a52fde77 AM |
635 | CoreNode thisNode = (CoreNode) this; |
636 | writer.print(" " + thisNode.getNbChildren() + " children"); | |
637 | if (thisNode.getNbChildren() >= 1) { | |
638 | writer.print(": [ " + thisNode.getChild(0)); | |
639 | for (int i = 1; i < thisNode.getNbChildren(); i++) { | |
640 | writer.print(", " + thisNode.getChild(i)); | |
641 | } | |
642 | writer.print(']'); | |
643 | } | |
644 | writer.print('\n'); | |
645 | } | |
646 | ||
647 | /* List of intervals in the node */ | |
648 | writer.println(" Intervals contained:"); | |
649 | for (int i = 0; i < intervals.size(); i++) { | |
650 | writer.println(intervals.get(i).toString()); | |
651 | } | |
652 | writer.println('\n'); | |
653 | } | |
654 | ||
6f4e8ec0 AM |
655 | // ------------------------------------------------------------------------ |
656 | // Abstract methods | |
657 | // ------------------------------------------------------------------------ | |
a52fde77 | 658 | |
8d47cc34 AM |
659 | /** |
660 | * Get the byte value representing the node type. | |
661 | * | |
662 | * @return The node type | |
663 | */ | |
bb7f92ce | 664 | public abstract NodeType getNodeType(); |
a52fde77 | 665 | |
8d47cc34 | 666 | /** |
62197b87 AM |
667 | * Return the specific header size of this node. This means the size |
668 | * occupied by the type-specific section of the header (not counting the | |
669 | * common part). | |
8d47cc34 | 670 | * |
62197b87 | 671 | * @return The specific header size |
8d47cc34 | 672 | */ |
62197b87 | 673 | protected abstract int getSpecificHeaderSize(); |
a52fde77 | 674 | |
8d47cc34 AM |
675 | /** |
676 | * Read the type-specific part of the node header from a byte buffer. | |
677 | * | |
678 | * @param buffer | |
679 | * The byte buffer to read from. It should be already positioned | |
680 | * correctly. | |
681 | */ | |
62197b87 | 682 | protected abstract void readSpecificHeader(ByteBuffer buffer); |
a52fde77 | 683 | |
8d47cc34 AM |
684 | /** |
685 | * Write the type-specific part of the header in a byte buffer. | |
686 | * | |
687 | * @param buffer | |
688 | * The buffer to write to. It should already be at the correct | |
689 | * position. | |
690 | */ | |
62197b87 | 691 | protected abstract void writeSpecificHeader(ByteBuffer buffer); |
a52fde77 | 692 | |
8d47cc34 AM |
693 | /** |
694 | * Node-type-specific toString method. Used for debugging. | |
695 | * | |
696 | * @return A string representing the node | |
697 | */ | |
62197b87 | 698 | protected abstract String toStringSpecific(); |
a52fde77 | 699 | } |