Commit | Line | Data |
---|---|---|
a52fde77 | 1 | /******************************************************************************* |
b2ca67ca | 2 | * Copyright (c) 2010, 2016 Ericsson, École Polytechnique de Montréal, and others |
3b7f5abe | 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 | |
3b7f5abe | 8 | * |
bb7f92ce FW |
9 | * Contributors: |
10 | * Alexandre Montplaisir - Initial API and implementation | |
11 | * Florian Wininger - Add Extension and Leaf Node | |
e13bd4cd | 12 | * Patrick Tasse - Add message to exceptions |
a52fde77 AM |
13 | *******************************************************************************/ |
14 | ||
e894a508 | 15 | package org.eclipse.tracecompass.internal.statesystem.core.backend.historytree; |
a52fde77 AM |
16 | |
17 | import java.io.File; | |
18 | import java.io.FileInputStream; | |
19 | import java.io.IOException; | |
20 | import java.io.PrintWriter; | |
21 | import java.nio.ByteBuffer; | |
22 | import java.nio.ByteOrder; | |
3b7f5abe | 23 | import java.nio.channels.ClosedChannelException; |
a52fde77 | 24 | import java.nio.channels.FileChannel; |
cb42195c AM |
25 | import java.util.ArrayList; |
26 | import java.util.Collections; | |
27 | import java.util.List; | |
a52fde77 | 28 | |
aa353506 | 29 | import org.eclipse.jdt.annotation.NonNull; |
e894a508 AM |
30 | import org.eclipse.tracecompass.internal.statesystem.core.Activator; |
31 | import org.eclipse.tracecompass.statesystem.core.ITmfStateSystemBuilder; | |
32 | import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException; | |
a52fde77 | 33 | |
a10a38ae | 34 | import com.google.common.annotations.VisibleForTesting; |
f3476b68 GB |
35 | import com.google.common.collect.ImmutableList; |
36 | ||
a52fde77 AM |
37 | /** |
38 | * Meta-container for the History Tree. This structure contains all the | |
39 | * high-level data relevant to the tree. | |
3b7f5abe | 40 | * |
ffd0aa67 | 41 | * @author Alexandre Montplaisir |
a52fde77 | 42 | */ |
3a081e85 | 43 | public class HistoryTreeClassic implements IHistoryTree { |
cb42195c | 44 | |
9a4c098d GB |
45 | /** |
46 | * The magic number for this file format. Package-private for the factory | |
47 | * class. | |
48 | */ | |
49 | static final int HISTORY_FILE_MAGIC_NUMBER = 0x05FFA900; | |
a52fde77 | 50 | |
a96cc6be | 51 | /** File format version. Increment when breaking compatibility. */ |
8182f36f | 52 | private static final int FILE_VERSION = 7; |
a52fde77 | 53 | |
dbdc452f AM |
54 | // ------------------------------------------------------------------------ |
55 | // Tree-specific configuration | |
56 | // ------------------------------------------------------------------------ | |
57 | ||
58 | /** Container for all the configuration constants */ | |
0e9b2f07 | 59 | private final HTConfig fConfig; |
a52fde77 | 60 | |
dbdc452f | 61 | /** Reader/writer object */ |
0e9b2f07 | 62 | private final HT_IO fTreeIO; |
a52fde77 | 63 | |
dbdc452f | 64 | // ------------------------------------------------------------------------ |
ecb12461 | 65 | // Variable Fields (will change throughout the existence of the SHT) |
dbdc452f AM |
66 | // ------------------------------------------------------------------------ |
67 | ||
68 | /** Latest timestamp found in the tree (at any given moment) */ | |
0e9b2f07 | 69 | private long fTreeEnd; |
a52fde77 | 70 | |
360f899e | 71 | /** The total number of nodes that exists in this tree */ |
0e9b2f07 | 72 | private int fNodeCount; |
a52fde77 | 73 | |
dbdc452f | 74 | /** "Cache" to keep the active nodes in memory */ |
367e2932 | 75 | private final @NonNull List<@NonNull HTNode> fLatestBranch; |
a52fde77 | 76 | |
dbdc452f AM |
77 | // ------------------------------------------------------------------------ |
78 | // Constructors/"Destructors" | |
79 | // ------------------------------------------------------------------------ | |
80 | ||
a52fde77 | 81 | /** |
8d47cc34 AM |
82 | * Create a new State History from scratch, using a {@link HTConfig} object |
83 | * for configuration. | |
84 | * | |
85 | * @param conf | |
86 | * The config to use for this History Tree. | |
87 | * @throws IOException | |
88 | * If an error happens trying to open/write to the file | |
89 | * specified in the config | |
a52fde77 | 90 | */ |
3a081e85 | 91 | public HistoryTreeClassic(HTConfig conf) throws IOException { |
a52fde77 | 92 | /* |
bb7f92ce FW |
93 | * Simple check to make sure we have enough place in the 0th block for |
94 | * the tree configuration | |
a52fde77 | 95 | */ |
cb42195c | 96 | if (conf.getBlockSize() < TREE_HEADER_SIZE) { |
dbdc452f AM |
97 | throw new IllegalArgumentException(); |
98 | } | |
a52fde77 | 99 | |
0e9b2f07 GB |
100 | fConfig = conf; |
101 | fTreeEnd = conf.getTreeStart(); | |
102 | fNodeCount = 0; | |
aa353506 | 103 | fLatestBranch = Collections.synchronizedList(new ArrayList<>()); |
a52fde77 AM |
104 | |
105 | /* Prepare the IO object */ | |
0e9b2f07 | 106 | fTreeIO = new HT_IO(fConfig, true); |
a52fde77 AM |
107 | |
108 | /* Add the first node to the tree */ | |
bb7f92ce | 109 | LeafNode firstNode = initNewLeafNode(-1, conf.getTreeStart()); |
0e9b2f07 | 110 | fLatestBranch.add(firstNode); |
a52fde77 AM |
111 | } |
112 | ||
a52fde77 AM |
113 | /** |
114 | * "Reader" constructor : instantiate a SHTree from an existing tree file on | |
115 | * disk | |
3b7f5abe | 116 | * |
8d47cc34 | 117 | * @param existingStateFile |
a52fde77 | 118 | * Path/filename of the history-file we are to open |
a96cc6be AM |
119 | * @param expProviderVersion |
120 | * The expected version of the state provider | |
a52fde77 | 121 | * @throws IOException |
8d47cc34 | 122 | * If an error happens reading the file |
a52fde77 | 123 | */ |
3a081e85 | 124 | public HistoryTreeClassic(File existingStateFile, int expProviderVersion) throws IOException { |
a52fde77 AM |
125 | /* |
126 | * Open the file ourselves, get the tree header information we need, | |
127 | * then pass on the descriptor to the TreeIO object. | |
128 | */ | |
129 | int rootNodeSeqNb, res; | |
130 | int bs, maxc; | |
fb12b0c2 | 131 | long startTime; |
a52fde77 AM |
132 | |
133 | /* Java I/O mumbo jumbo... */ | |
fee997a5 AM |
134 | if (!existingStateFile.exists()) { |
135 | throw new IOException("Selected state file does not exist"); //$NON-NLS-1$ | |
136 | } | |
fb12b0c2 | 137 | if (existingStateFile.length() <= 0) { |
a96cc6be | 138 | throw new IOException("Empty target file"); //$NON-NLS-1$ |
a52fde77 AM |
139 | } |
140 | ||
a4524c1b AM |
141 | try (FileInputStream fis = new FileInputStream(existingStateFile); |
142 | FileChannel fc = fis.getChannel();) { | |
a52fde77 | 143 | |
a4524c1b | 144 | ByteBuffer buffer = ByteBuffer.allocate(TREE_HEADER_SIZE); |
a52fde77 | 145 | |
a4524c1b AM |
146 | buffer.order(ByteOrder.LITTLE_ENDIAN); |
147 | buffer.clear(); | |
148 | fc.read(buffer); | |
149 | buffer.flip(); | |
a52fde77 | 150 | |
a96cc6be | 151 | /* |
a4524c1b AM |
152 | * Check the magic number to make sure we're opening the right type |
153 | * of file | |
a96cc6be | 154 | */ |
a4524c1b AM |
155 | res = buffer.getInt(); |
156 | if (res != HISTORY_FILE_MAGIC_NUMBER) { | |
157 | throw new IOException("Wrong magic number"); //$NON-NLS-1$ | |
158 | } | |
159 | ||
160 | res = buffer.getInt(); /* File format version number */ | |
161 | if (res != FILE_VERSION) { | |
162 | throw new IOException("Mismatching History Tree file format versions"); //$NON-NLS-1$ | |
163 | } | |
164 | ||
165 | res = buffer.getInt(); /* Event handler's version number */ | |
166 | if (res != expProviderVersion && | |
bcec0116 | 167 | expProviderVersion != ITmfStateSystemBuilder.IGNORE_PROVIDER_VERSION) { |
a4524c1b AM |
168 | /* |
169 | * The existing history was built using an event handler that | |
170 | * doesn't match the current one in the framework. | |
171 | * | |
172 | * Information could be all wrong. Instead of keeping an | |
173 | * incorrect history file, a rebuild is done. | |
174 | */ | |
175 | throw new IOException("Mismatching event handler versions"); //$NON-NLS-1$ | |
176 | } | |
177 | ||
178 | bs = buffer.getInt(); /* Block Size */ | |
179 | maxc = buffer.getInt(); /* Max nb of children per node */ | |
a52fde77 | 180 | |
0e9b2f07 | 181 | fNodeCount = buffer.getInt(); |
a4524c1b AM |
182 | rootNodeSeqNb = buffer.getInt(); |
183 | startTime = buffer.getLong(); | |
a52fde77 | 184 | |
0e9b2f07 | 185 | fConfig = new HTConfig(existingStateFile, bs, maxc, expProviderVersion, startTime); |
a4524c1b | 186 | } |
a52fde77 | 187 | |
a52fde77 AM |
188 | /* |
189 | * FIXME We close fis here and the TreeIO will then reopen the same | |
190 | * file, not extremely elegant. But how to pass the information here to | |
191 | * the SHT otherwise? | |
192 | */ | |
0e9b2f07 | 193 | fTreeIO = new HT_IO(fConfig, false); |
a52fde77 | 194 | |
0e9b2f07 GB |
195 | fLatestBranch = buildLatestBranch(rootNodeSeqNb); |
196 | fTreeEnd = getRootNode().getNodeEnd(); | |
fb12b0c2 AM |
197 | |
198 | /* | |
199 | * Make sure the history start time we read previously is consistent | |
200 | * with was is actually in the root node. | |
201 | */ | |
412a0225 | 202 | if (startTime != getRootNode().getNodeStart()) { |
fb12b0c2 AM |
203 | throw new IOException("Inconsistent start times in the" + //$NON-NLS-1$ |
204 | "history file, it might be corrupted."); //$NON-NLS-1$ | |
205 | } | |
a52fde77 AM |
206 | } |
207 | ||
412a0225 AM |
208 | /** |
209 | * Rebuild the latestBranch "cache" object by reading the nodes from disk | |
210 | * (When we are opening an existing file on disk and want to append to it, | |
211 | * for example). | |
212 | * | |
213 | * @param rootNodeSeqNb | |
214 | * The sequence number of the root node, so we know where to | |
215 | * start | |
216 | * @throws ClosedChannelException | |
217 | */ | |
367e2932 | 218 | private @NonNull List<@NonNull HTNode> buildLatestBranch(int rootNodeSeqNb) throws ClosedChannelException { |
aa353506 | 219 | List<@NonNull HTNode> list = new ArrayList<>(); |
412a0225 | 220 | |
0e9b2f07 | 221 | HTNode nextChildNode = fTreeIO.readNode(rootNodeSeqNb); |
bb7f92ce | 222 | list.add(nextChildNode); |
412a0225 | 223 | |
bb7f92ce FW |
224 | /* Follow the last branch up to the leaf */ |
225 | while (nextChildNode.getNodeType() == HTNode.NodeType.CORE) { | |
0e9b2f07 | 226 | nextChildNode = fTreeIO.readNode(((CoreNode) nextChildNode).getLatestChild()); |
bb7f92ce | 227 | list.add(nextChildNode); |
412a0225 AM |
228 | } |
229 | return Collections.synchronizedList(list); | |
230 | } | |
231 | ||
3a081e85 | 232 | @Override |
8d47cc34 | 233 | public void closeTree(long requestedEndTime) { |
412a0225 | 234 | /* This is an important operation, queries can wait */ |
0e9b2f07 | 235 | synchronized (fLatestBranch) { |
412a0225 AM |
236 | /* |
237 | * Work-around the "empty branches" that get created when the root | |
238 | * node becomes full. Overwrite the tree's end time with the | |
239 | * original wanted end-time, to ensure no queries are sent into | |
240 | * those empty nodes. | |
241 | * | |
242 | * This won't be needed once extended nodes are implemented. | |
243 | */ | |
0e9b2f07 | 244 | fTreeEnd = requestedEndTime; |
6a1074ce | 245 | |
412a0225 | 246 | /* Close off the latest branch of the tree */ |
0e9b2f07 GB |
247 | for (int i = 0; i < fLatestBranch.size(); i++) { |
248 | fLatestBranch.get(i).closeThisNode(fTreeEnd); | |
249 | fTreeIO.writeNode(fLatestBranch.get(i)); | |
412a0225 | 250 | } |
a52fde77 | 251 | |
0e9b2f07 | 252 | try (FileChannel fc = fTreeIO.getFcOut();) { |
412a0225 AM |
253 | ByteBuffer buffer = ByteBuffer.allocate(TREE_HEADER_SIZE); |
254 | buffer.order(ByteOrder.LITTLE_ENDIAN); | |
255 | buffer.clear(); | |
a52fde77 | 256 | |
412a0225 AM |
257 | /* Save the config of the tree to the header of the file */ |
258 | fc.position(0); | |
a52fde77 | 259 | |
412a0225 | 260 | buffer.putInt(HISTORY_FILE_MAGIC_NUMBER); |
a52fde77 | 261 | |
412a0225 | 262 | buffer.putInt(FILE_VERSION); |
0e9b2f07 | 263 | buffer.putInt(fConfig.getProviderVersion()); |
a52fde77 | 264 | |
0e9b2f07 GB |
265 | buffer.putInt(fConfig.getBlockSize()); |
266 | buffer.putInt(fConfig.getMaxChildren()); | |
a52fde77 | 267 | |
0e9b2f07 | 268 | buffer.putInt(fNodeCount); |
a52fde77 | 269 | |
412a0225 | 270 | /* root node seq. nb */ |
0e9b2f07 | 271 | buffer.putInt(fLatestBranch.get(0).getSequenceNumber()); |
a52fde77 | 272 | |
412a0225 | 273 | /* start time of this history */ |
0e9b2f07 | 274 | buffer.putLong(fLatestBranch.get(0).getNodeStart()); |
fb12b0c2 | 275 | |
412a0225 AM |
276 | buffer.flip(); |
277 | int res = fc.write(buffer); | |
278 | assert (res <= TREE_HEADER_SIZE); | |
279 | /* done writing the file header */ | |
a52fde77 | 280 | |
412a0225 AM |
281 | } catch (IOException e) { |
282 | /* | |
283 | * If we were able to write so far, there should not be any | |
284 | * problem at this point... | |
285 | */ | |
286 | throw new RuntimeException("State system write error"); //$NON-NLS-1$ | |
287 | } | |
a52fde77 | 288 | } |
a52fde77 AM |
289 | } |
290 | ||
dbdc452f AM |
291 | // ------------------------------------------------------------------------ |
292 | // Accessors | |
293 | // ------------------------------------------------------------------------ | |
ab604305 | 294 | |
3a081e85 | 295 | @Override |
8d47cc34 | 296 | public long getTreeStart() { |
0e9b2f07 | 297 | return fConfig.getTreeStart(); |
a52fde77 AM |
298 | } |
299 | ||
3a081e85 | 300 | @Override |
8d47cc34 | 301 | public long getTreeEnd() { |
0e9b2f07 | 302 | return fTreeEnd; |
a52fde77 AM |
303 | } |
304 | ||
3a081e85 | 305 | @Override |
8d47cc34 | 306 | public int getNodeCount() { |
0e9b2f07 | 307 | return fNodeCount; |
a52fde77 AM |
308 | } |
309 | ||
3a081e85 | 310 | @Override |
bb7f92ce | 311 | public HTNode getRootNode() { |
0e9b2f07 | 312 | return fLatestBranch.get(0); |
cb42195c AM |
313 | } |
314 | ||
f3476b68 GB |
315 | /** |
316 | * Return the latest branch of the tree. That branch is immutable. Used for | |
317 | * unit testing and debugging. | |
318 | * | |
319 | * @return The immutable latest branch | |
320 | */ | |
a10a38ae | 321 | @VisibleForTesting |
aa353506 | 322 | protected List<@NonNull HTNode> getLatestBranch() { |
f3476b68 GB |
323 | return ImmutableList.copyOf(fLatestBranch); |
324 | } | |
325 | ||
a10a38ae GB |
326 | /** |
327 | * Read a node at sequence number | |
328 | * | |
329 | * @param seqNum | |
330 | * The sequence number of the node to read | |
331 | * @return The HTNode object | |
332 | * @throws ClosedChannelException | |
333 | * Exception thrown when reading the node | |
334 | */ | |
335 | @VisibleForTesting | |
336 | protected @NonNull HTNode getNode(int seqNum) throws ClosedChannelException { | |
337 | // First, check in the latest branch if the node is there | |
338 | for (HTNode node : fLatestBranch) { | |
339 | if (node.getSequenceNumber() == seqNum) { | |
340 | return node; | |
341 | } | |
342 | } | |
343 | return fTreeIO.readNode(seqNum); | |
344 | } | |
345 | ||
360f899e EB |
346 | // ------------------------------------------------------------------------ |
347 | // HT_IO interface | |
348 | // ------------------------------------------------------------------------ | |
349 | ||
3a081e85 | 350 | @Override |
8d47cc34 | 351 | public FileInputStream supplyATReader() { |
0e9b2f07 | 352 | return fTreeIO.supplyATReader(getNodeCount()); |
360f899e EB |
353 | } |
354 | ||
3a081e85 | 355 | @Override |
8d47cc34 | 356 | public File supplyATWriterFile() { |
0e9b2f07 | 357 | return fConfig.getStateFile(); |
360f899e EB |
358 | } |
359 | ||
3a081e85 | 360 | @Override |
8d47cc34 | 361 | public long supplyATWriterFilePos() { |
3a081e85 | 362 | return IHistoryTree.TREE_HEADER_SIZE |
0e9b2f07 | 363 | + ((long) getNodeCount() * fConfig.getBlockSize()); |
360f899e EB |
364 | } |
365 | ||
3a081e85 | 366 | @Override |
8d47cc34 | 367 | public HTNode readNode(int seqNumber) throws ClosedChannelException { |
360f899e | 368 | /* Try to read the node from memory */ |
0e9b2f07 GB |
369 | synchronized (fLatestBranch) { |
370 | for (HTNode node : fLatestBranch) { | |
412a0225 AM |
371 | if (node.getSequenceNumber() == seqNumber) { |
372 | return node; | |
373 | } | |
360f899e EB |
374 | } |
375 | } | |
376 | ||
377 | /* Read the node from disk */ | |
0e9b2f07 | 378 | return fTreeIO.readNode(seqNumber); |
360f899e EB |
379 | } |
380 | ||
3a081e85 | 381 | @Override |
8d47cc34 | 382 | public void writeNode(HTNode node) { |
0e9b2f07 | 383 | fTreeIO.writeNode(node); |
360f899e EB |
384 | } |
385 | ||
3a081e85 | 386 | @Override |
8d47cc34 | 387 | public void closeFile() { |
0e9b2f07 | 388 | fTreeIO.closeFile(); |
360f899e EB |
389 | } |
390 | ||
3a081e85 | 391 | @Override |
8d47cc34 | 392 | public void deleteFile() { |
0e9b2f07 | 393 | fTreeIO.deleteFile(); |
360f899e EB |
394 | } |
395 | ||
dbdc452f AM |
396 | // ------------------------------------------------------------------------ |
397 | // Operations | |
398 | // ------------------------------------------------------------------------ | |
399 | ||
3a081e85 | 400 | @Override |
8d47cc34 | 401 | public void insertInterval(HTInterval interval) throws TimeRangeException { |
0e9b2f07 GB |
402 | if (interval.getStartTime() < fConfig.getTreeStart()) { |
403 | throw new TimeRangeException("Interval Start:" + interval.getStartTime() + ", Config Start:" + fConfig.getTreeStart()); //$NON-NLS-1$ //$NON-NLS-2$ | |
a52fde77 | 404 | } |
0e9b2f07 | 405 | tryInsertAtNode(interval, fLatestBranch.size() - 1); |
a52fde77 AM |
406 | } |
407 | ||
408 | /** | |
409 | * Inner method to find in which node we should add the interval. | |
3b7f5abe | 410 | * |
a52fde77 AM |
411 | * @param interval |
412 | * The interval to add to the tree | |
413 | * @param indexOfNode | |
414 | * The index *in the latestBranch* where we are trying the | |
415 | * insertion | |
416 | */ | |
417 | private void tryInsertAtNode(HTInterval interval, int indexOfNode) { | |
0e9b2f07 | 418 | HTNode targetNode = fLatestBranch.get(indexOfNode); |
a52fde77 AM |
419 | |
420 | /* Verify if there is enough room in this node to store this interval */ | |
59d30d83 | 421 | if (interval.getSizeOnDisk() > targetNode.getNodeFreeSpace()) { |
a52fde77 AM |
422 | /* Nope, not enough room. Insert in a new sibling instead. */ |
423 | addSiblingNode(indexOfNode); | |
0e9b2f07 | 424 | tryInsertAtNode(interval, fLatestBranch.size() - 1); |
a52fde77 AM |
425 | return; |
426 | } | |
427 | ||
428 | /* Make sure the interval time range fits this node */ | |
429 | if (interval.getStartTime() < targetNode.getNodeStart()) { | |
430 | /* | |
431 | * No, this interval starts before the startTime of this node. We | |
432 | * need to check recursively in parents if it can fit. | |
433 | */ | |
a52fde77 AM |
434 | tryInsertAtNode(interval, indexOfNode - 1); |
435 | return; | |
436 | } | |
437 | ||
438 | /* | |
439 | * Ok, there is room, and the interval fits in this time slot. Let's add | |
440 | * it. | |
441 | */ | |
442 | targetNode.addInterval(interval); | |
443 | ||
444 | /* Update treeEnd if needed */ | |
0e9b2f07 GB |
445 | if (interval.getEndTime() > fTreeEnd) { |
446 | fTreeEnd = interval.getEndTime(); | |
a52fde77 | 447 | } |
a52fde77 AM |
448 | } |
449 | ||
450 | /** | |
451 | * Method to add a sibling to any node in the latest branch. This will add | |
452 | * children back down to the leaf level, if needed. | |
3b7f5abe | 453 | * |
a52fde77 AM |
454 | * @param indexOfNode |
455 | * The index in latestBranch where we start adding | |
456 | */ | |
457 | private void addSiblingNode(int indexOfNode) { | |
0e9b2f07 GB |
458 | synchronized (fLatestBranch) { |
459 | final long splitTime = fTreeEnd; | |
a52fde77 | 460 | |
0e9b2f07 | 461 | if (indexOfNode >= fLatestBranch.size()) { |
bb7f92ce FW |
462 | /* |
463 | * We need to make sure (indexOfNode - 1) doesn't get the last | |
464 | * node in the branch, because that one is a Leaf Node. | |
465 | */ | |
466 | throw new IllegalStateException(); | |
467 | } | |
a52fde77 | 468 | |
412a0225 AM |
469 | /* Check if we need to add a new root node */ |
470 | if (indexOfNode == 0) { | |
471 | addNewRootNode(); | |
472 | return; | |
473 | } | |
a52fde77 | 474 | |
412a0225 | 475 | /* Check if we can indeed add a child to the target parent */ |
0e9b2f07 | 476 | if (((CoreNode) fLatestBranch.get(indexOfNode - 1)).getNbChildren() == fConfig.getMaxChildren()) { |
412a0225 AM |
477 | /* If not, add a branch starting one level higher instead */ |
478 | addSiblingNode(indexOfNode - 1); | |
479 | return; | |
480 | } | |
a52fde77 | 481 | |
412a0225 | 482 | /* Split off the new branch from the old one */ |
0e9b2f07 GB |
483 | for (int i = indexOfNode; i < fLatestBranch.size(); i++) { |
484 | fLatestBranch.get(i).closeThisNode(splitTime); | |
485 | fTreeIO.writeNode(fLatestBranch.get(i)); | |
a52fde77 | 486 | |
0e9b2f07 | 487 | CoreNode prevNode = (CoreNode) fLatestBranch.get(i - 1); |
bb7f92ce FW |
488 | HTNode newNode; |
489 | ||
0e9b2f07 | 490 | switch (fLatestBranch.get(i).getNodeType()) { |
bb7f92ce FW |
491 | case CORE: |
492 | newNode = initNewCoreNode(prevNode.getSequenceNumber(), splitTime + 1); | |
493 | break; | |
494 | case LEAF: | |
495 | newNode = initNewLeafNode(prevNode.getSequenceNumber(), splitTime + 1); | |
496 | break; | |
497 | default: | |
498 | throw new IllegalStateException(); | |
499 | } | |
a52fde77 | 500 | |
bb7f92ce | 501 | prevNode.linkNewChild(newNode); |
0e9b2f07 | 502 | fLatestBranch.set(i, newNode); |
412a0225 | 503 | } |
a52fde77 | 504 | } |
a52fde77 AM |
505 | } |
506 | ||
507 | /** | |
508 | * Similar to the previous method, except here we rebuild a completely new | |
509 | * latestBranch | |
510 | */ | |
511 | private void addNewRootNode() { | |
0e9b2f07 | 512 | final long splitTime = fTreeEnd; |
a52fde77 | 513 | |
0e9b2f07 GB |
514 | HTNode oldRootNode = fLatestBranch.get(0); |
515 | CoreNode newRootNode = initNewCoreNode(-1, fConfig.getTreeStart()); | |
a52fde77 AM |
516 | |
517 | /* Tell the old root node that it isn't root anymore */ | |
518 | oldRootNode.setParentSequenceNumber(newRootNode.getSequenceNumber()); | |
519 | ||
520 | /* Close off the whole current latestBranch */ | |
412a0225 | 521 | |
0e9b2f07 GB |
522 | for (int i = 0; i < fLatestBranch.size(); i++) { |
523 | fLatestBranch.get(i).closeThisNode(splitTime); | |
524 | fTreeIO.writeNode(fLatestBranch.get(i)); | |
a52fde77 AM |
525 | } |
526 | ||
527 | /* Link the new root to its first child (the previous root node) */ | |
528 | newRootNode.linkNewChild(oldRootNode); | |
529 | ||
530 | /* Rebuild a new latestBranch */ | |
0e9b2f07 GB |
531 | int depth = fLatestBranch.size(); |
532 | fLatestBranch.clear(); | |
533 | fLatestBranch.add(newRootNode); | |
bb7f92ce FW |
534 | |
535 | // Create new coreNode | |
7c247a0f | 536 | for (int i = 1; i < depth; i++) { |
0e9b2f07 | 537 | CoreNode prevNode = (CoreNode) fLatestBranch.get(i - 1); |
b2ca67ca | 538 | CoreNode newNode = initNewCoreNode(prevNode.getSequenceNumber(), |
a52fde77 AM |
539 | splitTime + 1); |
540 | prevNode.linkNewChild(newNode); | |
0e9b2f07 | 541 | fLatestBranch.add(newNode); |
a52fde77 | 542 | } |
bb7f92ce FW |
543 | |
544 | // Create the new leafNode | |
7c247a0f | 545 | CoreNode prevNode = (CoreNode) fLatestBranch.get(depth - 1); |
b2ca67ca | 546 | LeafNode newNode = initNewLeafNode(prevNode.getSequenceNumber(), splitTime + 1); |
bb7f92ce | 547 | prevNode.linkNewChild(newNode); |
0e9b2f07 | 548 | fLatestBranch.add(newNode); |
a52fde77 AM |
549 | } |
550 | ||
551 | /** | |
bb7f92ce | 552 | * Add a new empty core node to the tree. |
3b7f5abe | 553 | * |
a52fde77 AM |
554 | * @param parentSeqNumber |
555 | * Sequence number of this node's parent | |
556 | * @param startTime | |
557 | * Start time of the new node | |
558 | * @return The newly created node | |
559 | */ | |
aa353506 | 560 | private @NonNull CoreNode initNewCoreNode(int parentSeqNumber, long startTime) { |
0e9b2f07 | 561 | CoreNode newNode = new CoreNode(fConfig, fNodeCount, parentSeqNumber, |
a52fde77 | 562 | startTime); |
0e9b2f07 | 563 | fNodeCount++; |
a52fde77 AM |
564 | return newNode; |
565 | } | |
566 | ||
bb7f92ce FW |
567 | /** |
568 | * Add a new empty leaf node to the tree. | |
569 | * | |
570 | * @param parentSeqNumber | |
571 | * Sequence number of this node's parent | |
572 | * @param startTime | |
573 | * Start time of the new node | |
574 | * @return The newly created node | |
575 | */ | |
aa353506 | 576 | private @NonNull LeafNode initNewLeafNode(int parentSeqNumber, long startTime) { |
0e9b2f07 | 577 | LeafNode newNode = new LeafNode(fConfig, fNodeCount, parentSeqNumber, |
bb7f92ce | 578 | startTime); |
0e9b2f07 | 579 | fNodeCount++; |
bb7f92ce FW |
580 | return newNode; |
581 | } | |
582 | ||
3a081e85 | 583 | @Override |
8d47cc34 | 584 | public HTNode selectNextChild(CoreNode currentNode, long t) throws ClosedChannelException { |
a52fde77 AM |
585 | assert (currentNode.getNbChildren() > 0); |
586 | int potentialNextSeqNb = currentNode.getSequenceNumber(); | |
587 | ||
588 | for (int i = 0; i < currentNode.getNbChildren(); i++) { | |
589 | if (t >= currentNode.getChildStart(i)) { | |
590 | potentialNextSeqNb = currentNode.getChild(i); | |
591 | } else { | |
592 | break; | |
593 | } | |
594 | } | |
d862bcb3 | 595 | |
a52fde77 AM |
596 | /* |
597 | * Once we exit this loop, we should have found a children to follow. If | |
598 | * we didn't, there's a problem. | |
599 | */ | |
822798a3 GB |
600 | if (potentialNextSeqNb == currentNode.getSequenceNumber()) { |
601 | throw new IllegalStateException("No next child node found"); //$NON-NLS-1$ | |
602 | } | |
a52fde77 AM |
603 | |
604 | /* | |
605 | * Since this code path is quite performance-critical, avoid iterating | |
606 | * through the whole latestBranch array if we know for sure the next | |
607 | * node has to be on disk | |
608 | */ | |
045badfe | 609 | if (currentNode.isOnDisk()) { |
0e9b2f07 | 610 | return fTreeIO.readNode(potentialNextSeqNb); |
a52fde77 | 611 | } |
83c31d28 | 612 | return readNode(potentialNextSeqNb); |
a52fde77 AM |
613 | } |
614 | ||
3a081e85 | 615 | @Override |
8d47cc34 | 616 | public long getFileSize() { |
0e9b2f07 | 617 | return fConfig.getStateFile().length(); |
a52fde77 AM |
618 | } |
619 | ||
3b7f5abe AM |
620 | // ------------------------------------------------------------------------ |
621 | // Test/debugging methods | |
622 | // ------------------------------------------------------------------------ | |
a52fde77 | 623 | |
a52fde77 AM |
624 | /* Only used for debugging, shouldn't be externalized */ |
625 | @SuppressWarnings("nls") | |
626 | @Override | |
627 | public String toString() { | |
628 | return "Information on the current tree:\n\n" + "Blocksize: " | |
0e9b2f07 GB |
629 | + fConfig.getBlockSize() + "\n" + "Max nb. of children per node: " |
630 | + fConfig.getMaxChildren() + "\n" + "Number of nodes: " + fNodeCount | |
631 | + "\n" + "Depth of the tree: " + fLatestBranch.size() + "\n" | |
632 | + "Size of the treefile: " + getFileSize() + "\n" | |
a52fde77 | 633 | + "Root node has sequence number: " |
0e9b2f07 | 634 | + fLatestBranch.get(0).getSequenceNumber() + "\n" |
a52fde77 | 635 | + "'Latest leaf' has sequence number: " |
0e9b2f07 | 636 | + fLatestBranch.get(fLatestBranch.size() - 1).getSequenceNumber(); |
a52fde77 AM |
637 | } |
638 | ||
a52fde77 AM |
639 | /** |
640 | * Start at currentNode and print the contents of all its children, in | |
641 | * pre-order. Give the root node in parameter to visit the whole tree, and | |
642 | * have a nice overview. | |
643 | */ | |
d862bcb3 | 644 | /* Only used for debugging, shouldn't be externalized */ |
a52fde77 AM |
645 | @SuppressWarnings("nls") |
646 | private void preOrderPrint(PrintWriter writer, boolean printIntervals, | |
dbf883bb | 647 | HTNode currentNode, int curDepth, long ts) { |
a52fde77 AM |
648 | |
649 | writer.println(currentNode.toString()); | |
dbf883bb GB |
650 | /* Print intervals only if timestamp is negative or within the range of the node */ |
651 | if (printIntervals && | |
652 | (ts <= 0 || | |
653 | (ts >= currentNode.getNodeStart() && ts <= currentNode.getNodeEnd()))) { | |
a52fde77 AM |
654 | currentNode.debugPrintIntervals(writer); |
655 | } | |
a52fde77 | 656 | |
bb7f92ce FW |
657 | switch (currentNode.getNodeType()) { |
658 | case LEAF: | |
659 | /* Stop if it's the leaf node */ | |
660 | return; | |
661 | ||
662 | case CORE: | |
663 | try { | |
664 | final CoreNode node = (CoreNode) currentNode; | |
665 | /* Print the extensions, if any */ | |
666 | int extension = node.getExtensionSequenceNumber(); | |
667 | while (extension != -1) { | |
0e9b2f07 | 668 | HTNode nextNode = fTreeIO.readNode(extension); |
dbf883bb | 669 | preOrderPrint(writer, printIntervals, nextNode, curDepth, ts); |
bb7f92ce FW |
670 | } |
671 | ||
672 | /* Print the child nodes */ | |
673 | for (int i = 0; i < node.getNbChildren(); i++) { | |
0e9b2f07 | 674 | HTNode nextNode = fTreeIO.readNode(node.getChild(i)); |
bb7f92ce FW |
675 | for (int j = 0; j < curDepth; j++) { |
676 | writer.print(" "); | |
677 | } | |
678 | writer.print("+-"); | |
dbf883bb | 679 | preOrderPrint(writer, printIntervals, nextNode, curDepth + 1, ts); |
3b7f5abe | 680 | } |
bb7f92ce | 681 | } catch (ClosedChannelException e) { |
bcec0116 | 682 | Activator.getDefault().logError(e.getMessage()); |
a52fde77 | 683 | } |
bb7f92ce FW |
684 | break; |
685 | ||
686 | default: | |
687 | break; | |
a52fde77 | 688 | } |
a52fde77 AM |
689 | } |
690 | ||
3a081e85 | 691 | @Override |
dbf883bb | 692 | public void debugPrintFullTree(PrintWriter writer, boolean printIntervals, long ts) { |
a52fde77 | 693 | /* Only used for debugging, shouldn't be externalized */ |
d862bcb3 | 694 | |
dbf883bb | 695 | preOrderPrint(writer, false, fLatestBranch.get(0), 0, ts); |
a52fde77 AM |
696 | |
697 | if (printIntervals) { | |
dbf883bb | 698 | preOrderPrint(writer, true, fLatestBranch.get(0), 0, ts); |
a52fde77 AM |
699 | } |
700 | writer.println('\n'); | |
701 | } | |
702 | ||
703 | } |