1 /*******************************************************************************
2 * Copyright (c) 2017 École Polytechnique de Montréal
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
8 *******************************************************************************/
10 package org
.eclipse
.tracecompass
.internal
.provisional
.datastore
.core
.historytree
;
13 import java
.io
.FileInputStream
;
14 import java
.io
.FileOutputStream
;
15 import java
.io
.IOException
;
16 import java
.nio
.ByteBuffer
;
17 import java
.nio
.ByteOrder
;
18 import java
.nio
.channels
.ClosedChannelException
;
19 import java
.nio
.channels
.FileChannel
;
20 import java
.util
.ArrayList
;
21 import java
.util
.Collection
;
22 import java
.util
.Collections
;
23 import java
.util
.Deque
;
24 import java
.util
.LinkedList
;
25 import java
.util
.List
;
26 import java
.util
.concurrent
.locks
.ReentrantReadWriteLock
;
27 import java
.util
.function
.Predicate
;
29 import org
.eclipse
.jdt
.annotation
.NonNull
;
30 import org
.eclipse
.jdt
.annotation
.Nullable
;
31 import org
.eclipse
.tracecompass
.common
.core
.NonNullUtils
;
32 import org
.eclipse
.tracecompass
.internal
.datastore
.core
.historytree
.HtIo
;
33 import org
.eclipse
.tracecompass
.internal
.provisional
.datastore
.core
.condition
.TimeRangeCondition
;
34 import org
.eclipse
.tracecompass
.internal
.provisional
.datastore
.core
.exceptions
.RangeException
;
35 import org
.eclipse
.tracecompass
.internal
.provisional
.datastore
.core
.historytree
.IHTNode
.NodeType
;
36 import org
.eclipse
.tracecompass
.internal
.provisional
.datastore
.core
.interval
.IHTInterval
;
37 import org
.eclipse
.tracecompass
.internal
.provisional
.datastore
.core
.interval
.IHTIntervalReader
;
39 import com
.google
.common
.annotations
.VisibleForTesting
;
40 import com
.google
.common
.collect
.ImmutableList
;
41 import com
.google
.common
.collect
.Iterables
;
44 * Base class for history trees that encapsulates the logic to read from/write
47 * @author Alexandre Montplaisir
48 * @author Geneviève Bastien
50 * The type of intervals that will be saved in the tree
52 * The base type of the nodes of this tree
54 public abstract class AbstractHistoryTree
<E
extends IHTInterval
, N
extends HTNode
<E
>>
55 implements IHistoryTree
<E
> {
58 * Interface for history to create the various HTNodes
61 * The type of intervals that will be saved in the node
63 * The base type of the nodes of this tree
66 public interface IHTNodeFactory
<E
extends IHTInterval
, N
extends HTNode
<E
>> {
69 * Creates a new node for the specific history tree
72 * The type of node to create. See {@link IHTNode.NodeType}.
74 * The size (in bytes) of each node once serialized to disk
76 * The maximum number of amount a single core node can have
78 * The (unique) sequence number assigned to this particular
80 * @param parentSeqNumber
81 * The sequence number of this node's parent node
83 * The earliest timestamp stored in this node
84 * @return The new core node
86 N
createNode(NodeType type
, int blockSize
, int maxChildren
,
87 int seqNumber
, int parentSeqNumber
, long start
);
90 // ------------------------------------------------------------------------
91 // Tree-specific configuration
92 // ------------------------------------------------------------------------
94 /* Tree configuration constants */
95 private final File fHistoryFile
;
96 private final int fBlockSize
;
97 private final int fMaxChildren
;
98 private final int fProviderVersion
;
99 private final long fTreeStart
;
100 private final IHTIntervalReader
<E
> fIntervalReader
;
102 /** Reader/writer object */
103 private HtIo
<E
, N
> fTreeIO
;
105 // ------------------------------------------------------------------------
106 // Variable Fields (will change throughout the existence of the SHT)
107 // ------------------------------------------------------------------------
109 /** Latest timestamp found in the tree (at any given moment) */
110 private long fTreeEnd
;
112 /** The total number of nodes that exists in this tree */
113 private int fNodeCount
;
115 /** "Cache" to keep the active nodes in memory */
116 private final List
<N
> fLatestBranch
;
118 /* Lock used to protect the accesses to the HT_IO object */
119 private final ReentrantReadWriteLock fRwl
= new ReentrantReadWriteLock(false);
121 private transient @Nullable List
<N
> fLatestBranchSnapshot
= null;
124 * Create a new State History from scratch, specifying all configuration
127 * @param stateHistoryFile
128 * The name of the history file
130 * The size of each "block" on disk in bytes. One node will
131 * always fit in one block. It should be at least 4096.
133 * The maximum number of children allowed per core (non-leaf)
135 * @param providerVersion
136 * The version of the state provider. If a file already exists,
137 * and their versions match, the history file will not be rebuilt
140 * The start time of the history
141 * @param intervalReader
142 * The factory to create new tree intervals when reading from
144 * @throws IOException
145 * If an error happens trying to open/write to the file
146 * specified in the config
148 public AbstractHistoryTree(File stateHistoryFile
,
153 IHTIntervalReader
<E
> intervalReader
) throws IOException
{
155 * Simple check to make sure we have enough place in the 0th block for
156 * the tree configuration
158 if (blockSize
< TREE_HEADER_SIZE
) {
159 throw new IllegalArgumentException();
162 fHistoryFile
= stateHistoryFile
;
163 fBlockSize
= blockSize
;
164 fMaxChildren
= maxChildren
;
165 fProviderVersion
= providerVersion
;
166 fTreeStart
= treeStart
;
167 fIntervalReader
= intervalReader
;
169 fTreeEnd
= treeStart
;
171 fLatestBranch
= NonNullUtils
.checkNotNull(Collections
.synchronizedList(new ArrayList
<>()));
173 /* Prepare the IO object */
174 fTreeIO
= new HtIo
<>(stateHistoryFile
,
181 /* Add the first node to the tree */
182 N firstNode
= initNewLeafNode(-1, treeStart
);
183 fLatestBranch
.add(firstNode
);
187 * "Reader" constructor : instantiate a SHTree from an existing tree file on
190 * @param existingStateFile
191 * Path/filename of the history-file we are to open
192 * @param expectedProviderVersion
193 * The expected version of the state provider
194 * @param intervalReader
195 * The factory used to read segments from the history tree
196 * @throws IOException
197 * If an error happens reading the file
199 public AbstractHistoryTree(File existingStateFile
,
200 int expectedProviderVersion
,
201 IHTIntervalReader
<E
> intervalReader
) throws IOException
{
203 * Open the file ourselves, get the tree header information we need,
204 * then pass on the descriptor to the TreeIO object.
206 int rootNodeSeqNb
, res
;
210 /* Java I/O mumbo jumbo... */
211 if (!existingStateFile
.exists()) {
212 throw new IOException("Selected state file does not exist"); //$NON-NLS-1$
214 if (existingStateFile
.length() <= 0) {
215 throw new IOException("Empty target file"); //$NON-NLS-1$
218 try (FileInputStream fis
= new FileInputStream(existingStateFile
);
219 FileChannel fc
= fis
.getChannel();) {
221 ByteBuffer buffer
= ByteBuffer
.allocate(TREE_HEADER_SIZE
);
222 buffer
.order(ByteOrder
.LITTLE_ENDIAN
);
225 res
= fc
.read(buffer
);
226 if (res
!= TREE_HEADER_SIZE
) {
227 throw new IOException("Invalid header size"); //$NON-NLS-1$
233 * Check the magic number to make sure we're opening the right type
236 res
= buffer
.getInt();
237 if (res
!= getMagicNumber()) {
238 throw new IOException("Wrong magic number"); //$NON-NLS-1$
241 res
= buffer
.getInt(); /* File format version number */
242 if (res
!= getFileVersion()) {
243 throw new IOException("Mismatching History Tree file format versions"); //$NON-NLS-1$
246 res
= buffer
.getInt(); /* Event handler's version number */
247 if (res
!= expectedProviderVersion
) {
249 * The existing history was built using an event handler that
250 * doesn't match the current one in the framework.
252 * Information could be all wrong. Instead of keeping an
253 * incorrect history file, a rebuild is done.
255 throw new IOException("Mismatching event handler versions"); //$NON-NLS-1$
258 bs
= buffer
.getInt(); /* Block Size */
259 maxc
= buffer
.getInt(); /* Max nb of children per node */
261 fNodeCount
= buffer
.getInt();
262 rootNodeSeqNb
= buffer
.getInt();
263 startTime
= buffer
.getLong();
265 /* Set all other permanent configuration options */
266 fHistoryFile
= existingStateFile
;
269 fProviderVersion
= expectedProviderVersion
;
270 fIntervalReader
= intervalReader
;
271 fTreeStart
= startTime
;
275 * FIXME We close fis here and the TreeIO will then reopen the same
276 * file, not extremely elegant. But how to pass the information here to
279 fTreeIO
= new HtIo
<>(fHistoryFile
,
286 fLatestBranch
= buildLatestBranch(rootNodeSeqNb
);
287 fTreeEnd
= getRootNode().getNodeEnd();
290 * Make sure the history start time we read previously is consistent
291 * with was is actually in the root node.
293 if (startTime
!= getRootNode().getNodeStart()) {
294 throw new IOException("Inconsistent start times in the " + //$NON-NLS-1$
295 "history file, it might be corrupted."); //$NON-NLS-1$
300 * Rebuild the latestBranch "cache" object by reading the nodes from disk
301 * (When we are opening an existing file on disk and want to append to it,
304 * @param rootNodeSeqNb
305 * The sequence number of the root node, so we know where to
307 * @throws ClosedChannelException
309 private List
<N
> buildLatestBranch(int rootNodeSeqNb
) throws ClosedChannelException
{
310 List
<N
> list
= new ArrayList
<>();
312 N nextChildNode
= fTreeIO
.readNode(rootNodeSeqNb
);
313 list
.add(nextChildNode
);
315 // TODO: Do we need the full latest branch? The latest leaf may not be
316 // the one we'll query first... Won't it build itself later?
318 /* Follow the last branch up to the leaf */
319 while (nextChildNode
.getNodeType() == HTNode
.NodeType
.CORE
) {
320 nextChildNode
= fTreeIO
.readNode(nextChildNode
.getLatestChild());
321 list
.add(nextChildNode
);
323 return Collections
.synchronizedList(list
);
326 // ------------------------------------------------------------------------
328 // ------------------------------------------------------------------------
331 public long getTreeStart() {
336 public long getTreeEnd() {
341 * Get the number of nodes in this tree.
343 * @return The number of nodes
345 public int getNodeCount() {
350 * Get the current root node of this tree
352 * @return The root node
354 public N
getRootNode() {
355 return fLatestBranch
.get(0);
359 public long getFileSize() {
360 return fHistoryFile
.length();
364 * Return the latest branch of the tree. That branch is immutable.
366 * @return The immutable latest branch
369 final List
<N
> getLatestBranch() {
370 List
<N
> latestBranchSnapshot
= fLatestBranchSnapshot
;
371 if (latestBranchSnapshot
== null) {
372 synchronized (fLatestBranch
) {
373 latestBranchSnapshot
= ImmutableList
.copyOf(fLatestBranch
);
374 fLatestBranchSnapshot
= latestBranchSnapshot
;
377 return latestBranchSnapshot
;
381 * Get the node in the latest branch at a depth. If the depth is too large,
382 * it will throw an IndexOutOfBoundsException
385 * The depth at which to get the node
386 * @return The node at depth
388 protected N
getLatestNode(int depth
) {
389 if (depth
> fLatestBranch
.size()) {
390 throw new IndexOutOfBoundsException("Trying to get latest node too deep"); //$NON-NLS-1$
392 return fLatestBranch
.get(depth
);
396 * Get the magic number for the history file. This number should be specific
397 * for each implementation of the history tree.
399 * @return The magic number for the history file
401 protected abstract int getMagicNumber();
404 * Get the file version for the history file. This file version should be
405 * modified for a history tree class whenever changing the format of the
406 * file. Different versions of the file may not be compatible.
408 * @return The file version for the history file.
410 protected abstract int getFileVersion();
413 * Get the factory to use to create new nodes for this history tree.
415 * This method is called in the constructor of the abstract class, so
416 * assigning the factory to a final field may cause NullPointerException
417 * since that final field may not be initialized the first time this is
420 * @return The NodeFactory for the History Tree
422 protected abstract IHTNodeFactory
<E
, N
> getNodeFactory();
425 * Read a node with a given sequence number
428 * The sequence number of the node to read
429 * @return The HTNode object
430 * @throws ClosedChannelException
431 * Exception thrown when reading the node, if the file was
435 @NonNull N
getNode(int seqNum
) throws ClosedChannelException
{
436 // First, check in the latest branch if the node is there
437 for (N node
: fLatestBranch
) {
438 if (node
.getSequenceNumber() == seqNum
) {
442 return fTreeIO
.readNode(seqNum
);
446 * Retrieve the TreeIO object. Should only be used for testing.
451 HtIo
<E
, N
> getTreeIO() {
455 // ------------------------------------------------------------------------
457 // ------------------------------------------------------------------------
459 // TODO Remove from here
461 public FileInputStream
supplyATReader() {
462 fRwl
.readLock().lock();
464 return fTreeIO
.supplyATReader(getNodeCount());
466 fRwl
.readLock().unlock();
470 // TODO Remove from here
472 public File
supplyATWriterFile() {
476 // TODO Remove from here
478 public long supplyATWriterFilePos() {
479 return IHistoryTree
.TREE_HEADER_SIZE
480 + ((long) getNodeCount() * fBlockSize
);
484 * Read a node from the tree.
487 * The sequence number of the node to read
489 * @throws ClosedChannelException
490 * If the tree IO is unavailable
492 public N
readNode(int seqNumber
) throws ClosedChannelException
{
493 /* Try to read the node from memory */
494 synchronized (fLatestBranch
) {
495 for (N node
: fLatestBranch
) {
496 if (node
.getSequenceNumber() == seqNumber
) {
502 fRwl
.readLock().lock();
504 /* Read the node from disk */
505 return fTreeIO
.readNode(seqNumber
);
507 fRwl
.readLock().unlock();
512 * Write a node object to the history file.
515 * The node to write to disk
517 public void writeNode(N node
) {
518 fRwl
.readLock().lock();
520 fTreeIO
.writeNode(node
);
522 fRwl
.readLock().unlock();
527 * Close the history file.
530 public void closeFile() {
531 fRwl
.writeLock().lock();
536 fRwl
.writeLock().unlock();
541 * Delete the history file.
544 public void deleteFile() {
545 fRwl
.writeLock().lock();
547 fTreeIO
.deleteFile();
550 fRwl
.writeLock().unlock();
555 public void cleanFile() throws IOException
{
556 fRwl
.writeLock().lock();
559 fTreeIO
.deleteFile();
561 fTreeIO
= new HtIo
<>(fHistoryFile
,
569 /* Add the first node to the tree */
570 N firstNode
= initNewLeafNode(-1, fTreeStart
);
571 fLatestBranch
.add(firstNode
);
573 fRwl
.writeLock().unlock();
577 private void clearContent() {
578 // Re-initialize the content of the tree after the file is deleted or
581 fLatestBranch
.clear();
584 // ------------------------------------------------------------------------
586 // ------------------------------------------------------------------------
589 * Insert an interval in the tree.
592 * The interval to be inserted
593 * @throws RangeException
594 * If the start of end time of the interval are invalid
597 public synchronized void insert(E interval
) throws RangeException
{
598 if (interval
.getStart() < fTreeStart
) {
599 throw new RangeException("Interval Start:" + interval
.getStart() + ", Config Start:" + fTreeStart
); //$NON-NLS-1$ //$NON-NLS-2$
601 tryInsertAtNode(interval
, fLatestBranch
.size() - 1);
605 * Add a new empty core node to the tree.
607 * @param parentSeqNumber
608 * Sequence number of this node's parent
610 * Start time of the new node
611 * @return The newly created node
613 protected final N
initNewCoreNode(int parentSeqNumber
, long startTime
) {
614 N newNode
= getNodeFactory().createNode(NodeType
.CORE
, fBlockSize
, fMaxChildren
,
615 fNodeCount
, parentSeqNumber
, startTime
);
621 * Add a new empty leaf node to the tree.
623 * @param parentSeqNumber
624 * Sequence number of this node's parent
626 * Start time of the new node
627 * @return The newly created node
629 protected final N
initNewLeafNode(int parentSeqNumber
, long startTime
) {
630 N newNode
= getNodeFactory().createNode(NodeType
.LEAF
, fBlockSize
, fMaxChildren
,
631 fNodeCount
, parentSeqNumber
, startTime
);
637 * Inner method to find in which node we should add the interval.
640 * The interval to add to the tree
642 * The index *in the latestBranch* where we are trying the
645 protected final void tryInsertAtNode(E interval
, int depth
) {
646 N targetNode
= fLatestBranch
.get(depth
);
647 informInsertingAtDepth(depth
);
649 /* Verify if there is enough room in this node to store this interval */
650 if (interval
.getSizeOnDisk() > targetNode
.getNodeFreeSpace()) {
651 /* Nope, not enough room. Insert in a new sibling instead. */
652 addSiblingNode(depth
, getNewBranchStart(depth
, interval
));
653 tryInsertAtNode(interval
, getLatestBranch().size() - 1);
657 /* Make sure the interval time range fits this node */
658 if (interval
.getStart() < targetNode
.getNodeStart()) {
660 * No, this interval starts before the startTime of this node. We
661 * need to check recursively in parents if it can fit.
663 tryInsertAtNode(interval
, depth
- 1);
668 * Ok, there is room, and the interval fits in this time slot. Let's add
671 targetNode
.add(interval
);
673 updateEndTime(interval
);
677 * Informs the tree that the insertion is requested at a given depth. When
678 * this is called, the element is not yet inserted, but the last call to
679 * this for an element will represent the depth at which is was really
680 * inserted. By default, this method does nothing and should not be
681 * necessary for concrete implementations, but it can be used by unit tests
682 * to check to position of insertion of elements.
685 * The depth at which the last insertion was done
688 protected void informInsertingAtDepth(int depth
) {
693 * Get the start time of the new node of the branch that will be added
696 * Note that the depth is the depth of the last node that was filled and to
697 * which a sibling should be added. But depending on the returned start
698 * time, the actual new branch may start at a lower depth if the start time
699 * happens to be lesser than the parent's start time.
702 * The depth of the last node that was filled and at which the
703 * new branch should start.
705 * The interval that is about to be inserted
706 * @return The value that should be the start time of the sibling node
708 protected abstract long getNewBranchStart(int depth
, E interval
);
711 * Method to add a sibling to any node in the latest branch. This will add
712 * children back down to the leaf level, if needed.
715 * The depth in latestBranch where we start adding
716 * @param newNodeStartTime
717 * The start time of the new node
719 private final void addSiblingNode(int depth
, long newNodeStartTime
) {
720 synchronized (fLatestBranch
) {
721 final long splitTime
= fTreeEnd
;
722 fLatestBranchSnapshot
= null;
724 if (depth
>= fLatestBranch
.size()) {
726 * We need to make sure (indexOfNode - 1) doesn't get the last
727 * node in the branch, because that one is a Leaf Node.
729 throw new IllegalStateException();
732 /* Check if we need to add a new root node */
734 addNewRootNode(newNodeStartTime
);
739 * Check if we can indeed add a child to the target parent and if
740 * the new start time is not before the target parent.
742 if (fLatestBranch
.get(depth
- 1).getNbChildren() == fMaxChildren
||
743 newNodeStartTime
< fLatestBranch
.get(depth
- 1).getNodeStart()) {
744 /* If not, add a branch starting one level higher instead */
745 addSiblingNode(depth
- 1, newNodeStartTime
);
750 * Close nodes from the leaf up because some parent nodes may need
751 * to get updated when their children are closed
753 for (int i
= fLatestBranch
.size() - 1; i
>= depth
; i
--) {
754 fLatestBranch
.get(i
).closeThisNode(splitTime
);
755 fTreeIO
.writeNode(fLatestBranch
.get(i
));
758 /* Split off the new branch from the old one */
759 for (int i
= depth
; i
< fLatestBranch
.size(); i
++) {
760 N prevNode
= fLatestBranch
.get(i
- 1);
763 switch (fLatestBranch
.get(i
).getNodeType()) {
765 newNode
= initNewCoreNode(prevNode
.getSequenceNumber(), newNodeStartTime
);
768 newNode
= initNewLeafNode(prevNode
.getSequenceNumber(), newNodeStartTime
);
771 throw new IllegalStateException();
774 prevNode
.linkNewChild(newNode
);
775 fLatestBranch
.set(i
, newNode
);
781 * Similar to the previous method, except here we rebuild a completely new
784 private void addNewRootNode(long newNodeStartTime
) {
785 final long nodeEnd
= fTreeEnd
;
787 N oldRootNode
= fLatestBranch
.get(0);
788 N newRootNode
= initNewCoreNode(-1, fTreeStart
);
790 /* Tell the old root node that it isn't root anymore */
791 oldRootNode
.setParentSequenceNumber(newRootNode
.getSequenceNumber());
793 /* Close off the whole current latestBranch */
794 for (int i
= fLatestBranch
.size() - 1; i
>= 0; i
--) {
795 fLatestBranch
.get(i
).closeThisNode(nodeEnd
);
796 fTreeIO
.writeNode(fLatestBranch
.get(i
));
799 /* Link the new root to its first child (the previous root node) */
800 newRootNode
.linkNewChild(oldRootNode
);
802 /* Rebuild a new latestBranch */
803 int depth
= fLatestBranch
.size();
804 fLatestBranch
.clear();
805 fLatestBranch
.add(newRootNode
);
807 // Create new coreNode
808 for (int i
= 1; i
< depth
; i
++) {
809 N prevNode
= fLatestBranch
.get(i
- 1);
810 N newNode
= initNewCoreNode(prevNode
.getSequenceNumber(), newNodeStartTime
);
811 prevNode
.linkNewChild(newNode
);
812 fLatestBranch
.add(newNode
);
815 // Create the new leafNode
816 N prevNode
= fLatestBranch
.get(depth
- 1);
817 N newNode
= initNewLeafNode(prevNode
.getSequenceNumber(), newNodeStartTime
);
818 prevNode
.linkNewChild(newNode
);
819 fLatestBranch
.add(newNode
);
823 * Update the tree's end time with this interval data
826 * The interval that was just added to the tree
828 protected void updateEndTime(E interval
) {
829 fTreeEnd
= Math
.max(fTreeEnd
, interval
.getEnd());
833 public void closeTree(long requestedEndTime
) {
834 /* This is an important operation, queries can wait */
835 synchronized (fLatestBranch
) {
837 * Work-around the "empty branches" that get created when the root
838 * node becomes full. Overwrite the tree's end time with the
839 * original wanted end-time, to ensure no queries are sent into
842 fTreeEnd
= requestedEndTime
;
844 /* Close off the latest branch of the tree */
845 for (int i
= fLatestBranch
.size() - 1; i
>= 0; i
--) {
846 fLatestBranch
.get(i
).closeThisNode(fTreeEnd
);
847 fTreeIO
.writeNode(fLatestBranch
.get(i
));
850 try (FileOutputStream fc
= fTreeIO
.getFileWriter(-1);) {
851 ByteBuffer buffer
= ByteBuffer
.allocate(TREE_HEADER_SIZE
);
852 buffer
.order(ByteOrder
.LITTLE_ENDIAN
);
855 buffer
.putInt(getMagicNumber());
857 buffer
.putInt(getFileVersion());
858 buffer
.putInt(fProviderVersion
);
860 buffer
.putInt(fBlockSize
);
861 buffer
.putInt(fMaxChildren
);
863 buffer
.putInt(fNodeCount
);
865 /* root node seq. nb */
866 buffer
.putInt(fLatestBranch
.get(0).getSequenceNumber());
868 /* start time of this history */
869 buffer
.putLong(fLatestBranch
.get(0).getNodeStart());
872 fc
.write(buffer
.array());
873 /* done writing the file header */
875 } catch (IOException e
) {
877 * If we were able to write so far, there should not be any
878 * problem at this point...
880 throw new RuntimeException("State system write error"); //$NON-NLS-1$
886 public Iterable
<E
> getMatchingIntervals(TimeRangeCondition timeCondition
,
887 Predicate
<E
> extraPredicate
) {
889 // TODO Change this to evaluate the nodes lazily
891 List
<Iterable
<E
>> intervalsOfNodes
= new LinkedList
<>();
893 /* Queue is a stack of nodes containing nodes intersecting t */
894 Deque
<Integer
> queue
= new LinkedList
<>();
895 /* We start by reading the information in the root node */
896 queue
.add(getRootNode().getSequenceNumber());
898 /* Then we follow the down in the relevant children */
900 while (!queue
.isEmpty()) {
901 int sequenceNumber
= queue
.pop();
902 HTNode
<E
> currentNode
= readNode(sequenceNumber
);
903 TimeRangeCondition nodeCondition
= timeCondition
.subCondition(
904 currentNode
.getNodeStart(), currentNode
.getNodeEnd());
906 if (nodeCondition
== null) {
910 if (currentNode
.getNodeType() == HTNode
.NodeType
.CORE
) {
911 /* Here we add the relevant children nodes for BFS */
912 queue
.addAll(currentNode
.selectNextChildren(nodeCondition
));
914 Iterable
<E
> nodeIntervals
= currentNode
.getMatchingIntervals(nodeCondition
, extraPredicate
);
915 intervalsOfNodes
.add(nodeIntervals
);
917 } catch (ClosedChannelException e
) {
919 return Iterables
.concat(intervalsOfNodes
);
923 public @Nullable E
getMatchingInterval(TimeRangeCondition timeCondition
,
924 Predicate
<E
> extraPredicate
) {
926 /* Queue a stack of nodes containing nodes intersecting t */
927 Deque
<Integer
> queue
= new LinkedList
<>();
928 /* We start by reading the information in the root node */
929 queue
.add(getRootNode().getSequenceNumber());
931 /* Then we follow the down in the relevant children until we find the interval */
933 while (!queue
.isEmpty()) {
934 int sequenceNumber
= queue
.pop();
935 HTNode
<E
> currentNode
= readNode(sequenceNumber
);
937 @Nullable E interval
= currentNode
.getMatchingInterval(timeCondition
, extraPredicate
);
938 if (interval
!= null) {
942 if (currentNode
.getNodeType() == HTNode
.NodeType
.CORE
) {
943 /* Here we add the relevant children nodes for BFS */
944 queue
.addAll(currentNode
.selectNextChildren(timeCondition
));
947 } catch (ClosedChannelException e
) {
953 public String
toString() {
954 return "Information on the current tree:\n\n" + "Blocksize: " //$NON-NLS-1$ //$NON-NLS-2$
955 + fBlockSize
+ "\n" + "Max nb. of children per node: " //$NON-NLS-1$//$NON-NLS-2$
956 + fMaxChildren
+ "\n" + "Number of nodes: " + fNodeCount
//$NON-NLS-1$//$NON-NLS-2$
957 + "\n" + "Depth of the tree: " + fLatestBranch
.size() + "\n" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
958 + "Size of the treefile: " + getFileSize() + "\n" //$NON-NLS-1$//$NON-NLS-2$
959 + "Root node has sequence number: " //$NON-NLS-1$
960 + fLatestBranch
.get(0).getSequenceNumber() + "\n" //$NON-NLS-1$
961 + "'Latest leaf' has sequence number: " //$NON-NLS-1$
962 + fLatestBranch
.get(fLatestBranch
.size() - 1).getSequenceNumber();
966 // ------------------------------------------------------------------------
967 // Test-specific methods
968 // ------------------------------------------------------------------------
971 * Get the current depth of the tree.
973 * @return The current depth
976 protected int getDepth() {
977 return getLatestBranch().size();
981 * Get the leaf (bottom-most) node of the latest branch.
983 * @return The latest leaf
986 protected N
getLatestLeaf() {
987 List
<N
> latestBranch
= getLatestBranch();
988 return latestBranch
.get(latestBranch
.size() - 1);
992 * Verify a node's specific information about a child.
997 * The index of the child in the parent's extra data
999 * The child node to verify
1000 * @return False if a problem was found, true otherwise
1003 protected boolean verifyChildrenSpecific(N parent
,
1006 // Nothing to do for the default implementation
1011 * This method should verify in the whole time range of the parent node that
1012 * the child node appears or not as a next children for a given timestamp.
1018 * @return False if a problem was found, true otherwise
1021 protected boolean verifyIntersectingChildren(N parent
, N child
) {
1022 int childSequence
= child
.getSequenceNumber();
1023 boolean shouldBeInCollection
;
1024 Collection
<Integer
> nextChildren
;
1025 for (long t
= parent
.getNodeStart(); t
< parent
.getNodeEnd(); t
++) {
1026 shouldBeInCollection
= true;
1027 nextChildren
= parent
.selectNextChildren(TimeRangeCondition
.singleton(t
));
1028 if (shouldBeInCollection
!= nextChildren
.contains(childSequence
)) {