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