+/*******************************************************************************
+ * Copyright (c) 2010, 2014 Ericsson, École Polytechnique de Montréal, and others
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Alexandre Montplaisir - Initial API and implementation
+ * Florian Wininger - Add Extension and Leaf Node
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.internal.statesystem.core.backend.historytree;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.linuxtools.internal.statesystem.core.Activator;
+import org.eclipse.linuxtools.statesystem.core.ITmfStateSystemBuilder;
+import org.eclipse.linuxtools.statesystem.core.exceptions.TimeRangeException;
+
+/**
+ * Meta-container for the History Tree. This structure contains all the
+ * high-level data relevant to the tree.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class HistoryTree {
+
+ /**
+ * Size of the "tree header" in the tree-file The nodes will use this offset
+ * to know where they should be in the file. This should always be a
+ * multiple of 4K.
+ */
+ public static final int TREE_HEADER_SIZE = 4096;
+
+ private static final int HISTORY_FILE_MAGIC_NUMBER = 0x05FFA900;
+
+ /** File format version. Increment when breaking compatibility. */
+ private static final int FILE_VERSION = 4;
+
+ // ------------------------------------------------------------------------
+ // Tree-specific configuration
+ // ------------------------------------------------------------------------
+
+ /** Container for all the configuration constants */
+ private final HTConfig config;
+
+ /** Reader/writer object */
+ private final HT_IO treeIO;
+
+ // ------------------------------------------------------------------------
+ // Variable Fields (will change throughout the existence of the SHT)
+ // ------------------------------------------------------------------------
+
+ /** Latest timestamp found in the tree (at any given moment) */
+ private long treeEnd;
+
+ /** The total number of nodes that exists in this tree */
+ private int nodeCount;
+
+ /** "Cache" to keep the active nodes in memory */
+ private final List<HTNode> latestBranch;
+
+ // ------------------------------------------------------------------------
+ // Constructors/"Destructors"
+ // ------------------------------------------------------------------------
+
+ /**
+ * Create a new State History from scratch, using a {@link HTConfig} object
+ * for configuration.
+ *
+ * @param conf
+ * The config to use for this History Tree.
+ * @throws IOException
+ * If an error happens trying to open/write to the file
+ * specified in the config
+ */
+ public HistoryTree(HTConfig conf) throws IOException {
+ /*
+ * Simple check to make sure we have enough place in the 0th block for
+ * the tree configuration
+ */
+ if (conf.getBlockSize() < TREE_HEADER_SIZE) {
+ throw new IllegalArgumentException();
+ }
+
+ config = conf;
+ treeEnd = conf.getTreeStart();
+ nodeCount = 0;
+ latestBranch = Collections.synchronizedList(new ArrayList<HTNode>());
+
+ /* Prepare the IO object */
+ treeIO = new HT_IO(config, true);
+
+ /* Add the first node to the tree */
+ LeafNode firstNode = initNewLeafNode(-1, conf.getTreeStart());
+ latestBranch.add(firstNode);
+ }
+
+ /**
+ * "Reader" constructor : instantiate a SHTree from an existing tree file on
+ * disk
+ *
+ * @param existingStateFile
+ * Path/filename of the history-file we are to open
+ * @param expProviderVersion
+ * The expected version of the state provider
+ * @throws IOException
+ * If an error happens reading the file
+ */
+ public HistoryTree(File existingStateFile, int expProviderVersion) throws IOException {
+ /*
+ * Open the file ourselves, get the tree header information we need,
+ * then pass on the descriptor to the TreeIO object.
+ */
+ int rootNodeSeqNb, res;
+ int bs, maxc;
+ long startTime;
+
+ /* Java I/O mumbo jumbo... */
+ if (!existingStateFile.exists()) {
+ throw new IOException("Selected state file does not exist"); //$NON-NLS-1$
+ }
+ if (existingStateFile.length() <= 0) {
+ throw new IOException("Empty target file"); //$NON-NLS-1$
+ }
+
+ try (FileInputStream fis = new FileInputStream(existingStateFile);
+ FileChannel fc = fis.getChannel();) {
+
+ ByteBuffer buffer = ByteBuffer.allocate(TREE_HEADER_SIZE);
+
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ buffer.clear();
+ fc.read(buffer);
+ buffer.flip();
+
+ /*
+ * Check the magic number to make sure we're opening the right type
+ * of file
+ */
+ res = buffer.getInt();
+ if (res != HISTORY_FILE_MAGIC_NUMBER) {
+ throw new IOException("Wrong magic number"); //$NON-NLS-1$
+ }
+
+ res = buffer.getInt(); /* File format version number */
+ if (res != FILE_VERSION) {
+ throw new IOException("Mismatching History Tree file format versions"); //$NON-NLS-1$
+ }
+
+ res = buffer.getInt(); /* Event handler's version number */
+ if (res != expProviderVersion &&
+ expProviderVersion != ITmfStateSystemBuilder.IGNORE_PROVIDER_VERSION) {
+ /*
+ * The existing history was built using an event handler that
+ * doesn't match the current one in the framework.
+ *
+ * Information could be all wrong. Instead of keeping an
+ * incorrect history file, a rebuild is done.
+ */
+ throw new IOException("Mismatching event handler versions"); //$NON-NLS-1$
+ }
+
+ bs = buffer.getInt(); /* Block Size */
+ maxc = buffer.getInt(); /* Max nb of children per node */
+
+ this.nodeCount = buffer.getInt();
+ rootNodeSeqNb = buffer.getInt();
+ startTime = buffer.getLong();
+
+ this.config = new HTConfig(existingStateFile, bs, maxc, expProviderVersion, startTime);
+ }
+
+ /*
+ * FIXME We close fis here and the TreeIO will then reopen the same
+ * file, not extremely elegant. But how to pass the information here to
+ * the SHT otherwise?
+ */
+ this.treeIO = new HT_IO(config, false);
+
+ this.latestBranch = buildLatestBranch(rootNodeSeqNb);
+ this.treeEnd = getRootNode().getNodeEnd();
+
+ /*
+ * Make sure the history start time we read previously is consistent
+ * with was is actually in the root node.
+ */
+ if (startTime != getRootNode().getNodeStart()) {
+ throw new IOException("Inconsistent start times in the" + //$NON-NLS-1$
+ "history file, it might be corrupted."); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Rebuild the latestBranch "cache" object by reading the nodes from disk
+ * (When we are opening an existing file on disk and want to append to it,
+ * for example).
+ *
+ * @param rootNodeSeqNb
+ * The sequence number of the root node, so we know where to
+ * start
+ * @throws ClosedChannelException
+ */
+ private List<HTNode> buildLatestBranch(int rootNodeSeqNb) throws ClosedChannelException {
+ List<HTNode> list = new ArrayList<>();
+
+ HTNode nextChildNode = treeIO.readNode(rootNodeSeqNb);
+ list.add(nextChildNode);
+
+ /* Follow the last branch up to the leaf */
+ while (nextChildNode.getNodeType() == HTNode.NodeType.CORE) {
+ nextChildNode = treeIO.readNode(((CoreNode) nextChildNode).getLatestChild());
+ list.add(nextChildNode);
+ }
+ return Collections.synchronizedList(list);
+ }
+
+ /**
+ * "Save" the tree to disk. This method will cause the treeIO object to
+ * commit all nodes to disk and then return the RandomAccessFile descriptor
+ * so the Tree object can save its configuration into the header of the
+ * file.
+ *
+ * @param requestedEndTime
+ * The greatest timestamp present in the history tree
+ */
+ public void closeTree(long requestedEndTime) {
+ /* This is an important operation, queries can wait */
+ synchronized (latestBranch) {
+ /*
+ * Work-around the "empty branches" that get created when the root
+ * node becomes full. Overwrite the tree's end time with the
+ * original wanted end-time, to ensure no queries are sent into
+ * those empty nodes.
+ *
+ * This won't be needed once extended nodes are implemented.
+ */
+ this.treeEnd = requestedEndTime;
+
+ /* Close off the latest branch of the tree */
+ for (int i = 0; i < latestBranch.size(); i++) {
+ latestBranch.get(i).closeThisNode(treeEnd);
+ treeIO.writeNode(latestBranch.get(i));
+ }
+
+ try (FileChannel fc = treeIO.getFcOut();) {
+ ByteBuffer buffer = ByteBuffer.allocate(TREE_HEADER_SIZE);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ buffer.clear();
+
+ /* Save the config of the tree to the header of the file */
+ fc.position(0);
+
+ buffer.putInt(HISTORY_FILE_MAGIC_NUMBER);
+
+ buffer.putInt(FILE_VERSION);
+ buffer.putInt(config.getProviderVersion());
+
+ buffer.putInt(config.getBlockSize());
+ buffer.putInt(config.getMaxChildren());
+
+ buffer.putInt(nodeCount);
+
+ /* root node seq. nb */
+ buffer.putInt(latestBranch.get(0).getSequenceNumber());
+
+ /* start time of this history */
+ buffer.putLong(latestBranch.get(0).getNodeStart());
+
+ buffer.flip();
+ int res = fc.write(buffer);
+ assert (res <= TREE_HEADER_SIZE);
+ /* done writing the file header */
+
+ } catch (IOException e) {
+ /*
+ * If we were able to write so far, there should not be any
+ * problem at this point...
+ */
+ throw new RuntimeException("State system write error"); //$NON-NLS-1$
+ }
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Accessors
+ // ------------------------------------------------------------------------
+
+ /**
+ * Get the start time of this tree.
+ *
+ * @return The start time
+ */
+ public long getTreeStart() {
+ return config.getTreeStart();
+ }
+
+ /**
+ * Get the current end time of this tree.
+ *
+ * @return The end time
+ */
+ public long getTreeEnd() {
+ return treeEnd;
+ }
+
+ /**
+ * Get the number of nodes in this tree.
+ *
+ * @return The number of nodes
+ */
+ public int getNodeCount() {
+ return nodeCount;
+ }
+
+ /**
+ * Get the current root node of this tree
+ *
+ * @return The root node
+ */
+ public HTNode getRootNode() {
+ return latestBranch.get(0);
+ }
+
+ // ------------------------------------------------------------------------
+ // HT_IO interface
+ // ------------------------------------------------------------------------
+
+ /**
+ * Return the FileInputStream reader with which we will read an attribute
+ * tree (it will be sought to the correct position).
+ *
+ * @return The FileInputStream indicating the file and position from which
+ * the attribute tree can be read.
+ */
+ public FileInputStream supplyATReader() {
+ return treeIO.supplyATReader(getNodeCount());
+ }
+
+ /**
+ * Return the file to which we will write the attribute tree.
+ *
+ * @return The file to which we will write the attribute tree
+ */
+ public File supplyATWriterFile() {
+ return config.getStateFile();
+ }
+
+ /**
+ * Return the position in the file (given by {@link #supplyATWriterFile})
+ * where to start writing the attribute tree.
+ *
+ * @return The position in the file where to start writing
+ */
+ public long supplyATWriterFilePos() {
+ return HistoryTree.TREE_HEADER_SIZE
+ + ((long) getNodeCount() * config.getBlockSize());
+ }
+
+ /**
+ * Read a node from the tree.
+ *
+ * @param seqNumber
+ * The sequence number of the node to read
+ * @return The node
+ * @throws ClosedChannelException
+ * If the tree IO is unavailable
+ */
+ public HTNode readNode(int seqNumber) throws ClosedChannelException {
+ /* Try to read the node from memory */
+ synchronized (latestBranch) {
+ for (HTNode node : latestBranch) {
+ if (node.getSequenceNumber() == seqNumber) {
+ return node;
+ }
+ }
+ }
+
+ /* Read the node from disk */
+ return treeIO.readNode(seqNumber);
+ }
+
+ /**
+ * Write a node object to the history file.
+ *
+ * @param node
+ * The node to write to disk
+ */
+ public void writeNode(HTNode node) {
+ treeIO.writeNode(node);
+ }
+
+ /**
+ * Close the history file.
+ */
+ public void closeFile() {
+ treeIO.closeFile();
+ }
+
+ /**
+ * Delete the history file.
+ */
+ public void deleteFile() {
+ treeIO.deleteFile();
+ }
+
+ // ------------------------------------------------------------------------
+ // Operations
+ // ------------------------------------------------------------------------
+
+ /**
+ * Insert an interval in the tree.
+ *
+ * @param interval
+ * The interval to be inserted
+ * @throws TimeRangeException
+ * If the start of end time of the interval are invalid
+ */
+ public void insertInterval(HTInterval interval) throws TimeRangeException {
+ if (interval.getStartTime() < config.getTreeStart()) {
+ throw new TimeRangeException();
+ }
+ tryInsertAtNode(interval, latestBranch.size() - 1);
+ }
+
+ /**
+ * Inner method to find in which node we should add the interval.
+ *
+ * @param interval
+ * The interval to add to the tree
+ * @param indexOfNode
+ * The index *in the latestBranch* where we are trying the
+ * insertion
+ */
+ private void tryInsertAtNode(HTInterval interval, int indexOfNode) {
+ HTNode targetNode = latestBranch.get(indexOfNode);
+
+ /* Verify if there is enough room in this node to store this interval */
+ if (interval.getIntervalSize() > targetNode.getNodeFreeSpace()) {
+ /* Nope, not enough room. Insert in a new sibling instead. */
+ addSiblingNode(indexOfNode);
+ tryInsertAtNode(interval, latestBranch.size() - 1);
+ return;
+ }
+
+ /* Make sure the interval time range fits this node */
+ if (interval.getStartTime() < targetNode.getNodeStart()) {
+ /*
+ * No, this interval starts before the startTime of this node. We
+ * need to check recursively in parents if it can fit.
+ */
+ assert (indexOfNode >= 1);
+ tryInsertAtNode(interval, indexOfNode - 1);
+ return;
+ }
+
+ /*
+ * Ok, there is room, and the interval fits in this time slot. Let's add
+ * it.
+ */
+ targetNode.addInterval(interval);
+
+ /* Update treeEnd if needed */
+ if (interval.getEndTime() > this.treeEnd) {
+ this.treeEnd = interval.getEndTime();
+ }
+ }
+
+ /**
+ * Method to add a sibling to any node in the latest branch. This will add
+ * children back down to the leaf level, if needed.
+ *
+ * @param indexOfNode
+ * The index in latestBranch where we start adding
+ */
+ private void addSiblingNode(int indexOfNode) {
+ synchronized (latestBranch) {
+ final long splitTime = treeEnd;
+
+ if (indexOfNode >= latestBranch.size()) {
+ /*
+ * We need to make sure (indexOfNode - 1) doesn't get the last
+ * node in the branch, because that one is a Leaf Node.
+ */
+ throw new IllegalStateException();
+ }
+
+ /* Check if we need to add a new root node */
+ if (indexOfNode == 0) {
+ addNewRootNode();
+ return;
+ }
+
+ /* Check if we can indeed add a child to the target parent */
+ if (((CoreNode) latestBranch.get(indexOfNode - 1)).getNbChildren() == config.getMaxChildren()) {
+ /* If not, add a branch starting one level higher instead */
+ addSiblingNode(indexOfNode - 1);
+ return;
+ }
+
+ /* Split off the new branch from the old one */
+ for (int i = indexOfNode; i < latestBranch.size(); i++) {
+ latestBranch.get(i).closeThisNode(splitTime);
+ treeIO.writeNode(latestBranch.get(i));
+
+ CoreNode prevNode = (CoreNode) latestBranch.get(i - 1);
+ HTNode newNode;
+
+ switch (latestBranch.get(i).getNodeType()) {
+ case CORE:
+ newNode = initNewCoreNode(prevNode.getSequenceNumber(), splitTime + 1);
+ break;
+ case LEAF:
+ newNode = initNewLeafNode(prevNode.getSequenceNumber(), splitTime + 1);
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+
+ prevNode.linkNewChild(newNode);
+ latestBranch.set(i, newNode);
+ }
+ }
+ }
+
+ /**
+ * Similar to the previous method, except here we rebuild a completely new
+ * latestBranch
+ */
+ private void addNewRootNode() {
+ final long splitTime = this.treeEnd;
+
+ HTNode oldRootNode = latestBranch.get(0);
+ CoreNode newRootNode = initNewCoreNode(-1, config.getTreeStart());
+
+ /* Tell the old root node that it isn't root anymore */
+ oldRootNode.setParentSequenceNumber(newRootNode.getSequenceNumber());
+
+ /* Close off the whole current latestBranch */
+
+ for (int i = 0; i < latestBranch.size(); i++) {
+ latestBranch.get(i).closeThisNode(splitTime);
+ treeIO.writeNode(latestBranch.get(i));
+ }
+
+ /* Link the new root to its first child (the previous root node) */
+ newRootNode.linkNewChild(oldRootNode);
+
+ /* Rebuild a new latestBranch */
+ int depth = latestBranch.size();
+ latestBranch.clear();
+ latestBranch.add(newRootNode);
+
+ // Create new coreNode
+ for (int i = 1; i < depth + 1; i++) {
+ CoreNode prevNode = (CoreNode) latestBranch.get(i - 1);
+ CoreNode newNode = initNewCoreNode(prevNode.getParentSequenceNumber(),
+ splitTime + 1);
+ prevNode.linkNewChild(newNode);
+ latestBranch.add(newNode);
+ }
+
+ // Create the new leafNode
+ CoreNode prevNode = (CoreNode) latestBranch.get(depth);
+ LeafNode newNode = initNewLeafNode(prevNode.getParentSequenceNumber(), splitTime + 1);
+ prevNode.linkNewChild(newNode);
+ latestBranch.add(newNode);
+ }
+
+ /**
+ * Add a new empty core node to the tree.
+ *
+ * @param parentSeqNumber
+ * Sequence number of this node's parent
+ * @param startTime
+ * Start time of the new node
+ * @return The newly created node
+ */
+ private CoreNode initNewCoreNode(int parentSeqNumber, long startTime) {
+ CoreNode newNode = new CoreNode(config, this.nodeCount, parentSeqNumber,
+ startTime);
+ this.nodeCount++;
+
+ /* Update the treeEnd if needed */
+ if (startTime >= this.treeEnd) {
+ this.treeEnd = startTime + 1;
+ }
+ return newNode;
+ }
+
+ /**
+ * Add a new empty leaf node to the tree.
+ *
+ * @param parentSeqNumber
+ * Sequence number of this node's parent
+ * @param startTime
+ * Start time of the new node
+ * @return The newly created node
+ */
+ private LeafNode initNewLeafNode(int parentSeqNumber, long startTime) {
+ LeafNode newNode = new LeafNode(config, this.nodeCount, parentSeqNumber,
+ startTime);
+ this.nodeCount++;
+
+ /* Update the treeEnd if needed */
+ if (startTime >= this.treeEnd) {
+ this.treeEnd = startTime + 1;
+ }
+ return newNode;
+ }
+
+ /**
+ * Inner method to select the next child of the current node intersecting
+ * the given timestamp. Useful for moving down the tree following one
+ * branch.
+ *
+ * @param currentNode
+ * The node on which the request is made
+ * @param t
+ * The timestamp to choose which child is the next one
+ * @return The child node intersecting t
+ * @throws ClosedChannelException
+ * If the file channel was closed while we were reading the tree
+ */
+ public HTNode selectNextChild(CoreNode currentNode, long t) throws ClosedChannelException {
+ assert (currentNode.getNbChildren() > 0);
+ int potentialNextSeqNb = currentNode.getSequenceNumber();
+
+ for (int i = 0; i < currentNode.getNbChildren(); i++) {
+ if (t >= currentNode.getChildStart(i)) {
+ potentialNextSeqNb = currentNode.getChild(i);
+ } else {
+ break;
+ }
+ }
+
+ /*
+ * Once we exit this loop, we should have found a children to follow. If
+ * we didn't, there's a problem.
+ */
+ assert (potentialNextSeqNb != currentNode.getSequenceNumber());
+
+ /*
+ * Since this code path is quite performance-critical, avoid iterating
+ * through the whole latestBranch array if we know for sure the next
+ * node has to be on disk
+ */
+ if (currentNode.isOnDisk()) {
+ return treeIO.readNode(potentialNextSeqNb);
+ }
+ return readNode(potentialNextSeqNb);
+ }
+
+ /**
+ * Get the current size of the history file.
+ *
+ * @return The history file size
+ */
+ public long getFileSize() {
+ return config.getStateFile().length();
+ }
+
+ // ------------------------------------------------------------------------
+ // Test/debugging methods
+ // ------------------------------------------------------------------------
+
+ /**
+ * Debugging method to make sure all intervals contained in the given node
+ * have valid start and end times.
+ *
+ * @param zenode
+ * The node to check
+ * @return True if everything is fine, false if there is at least one
+ * invalid timestamp (end time < start time, time outside of the
+ * range of the node, etc.)
+ */
+ @SuppressWarnings("nls")
+ public boolean checkNodeIntegrity(HTNode zenode) {
+ /* Only used for debugging, shouldn't be externalized */
+ HTNode otherNode;
+ CoreNode node;
+ StringBuffer buf = new StringBuffer();
+ boolean ret = true;
+
+ // FIXME /* Only testing Core Nodes for now */
+ if (!(zenode instanceof CoreNode)) {
+ return true;
+ }
+
+ node = (CoreNode) zenode;
+
+ try {
+ /*
+ * Test that this node's start and end times match the start of the
+ * first child and the end of the last child, respectively
+ */
+ if (node.getNbChildren() > 0) {
+ otherNode = treeIO.readNode(node.getChild(0));
+ if (node.getNodeStart() != otherNode.getNodeStart()) {
+ buf.append("Start time of node (" + node.getNodeStart() + ") "
+ + "does not match start time of first child " + "("
+ + otherNode.getNodeStart() + "), " + "node #"
+ + otherNode.getSequenceNumber() + ")\n");
+ ret = false;
+ }
+ if (node.isOnDisk()) {
+ otherNode = treeIO.readNode(node.getLatestChild());
+ if (node.getNodeEnd() != otherNode.getNodeEnd()) {
+ buf.append("End time of node (" + node.getNodeEnd()
+ + ") does not match end time of last child ("
+ + otherNode.getNodeEnd() + ", node #"
+ + otherNode.getSequenceNumber() + ")\n");
+ ret = false;
+ }
+ }
+ }
+
+ /*
+ * Test that the childStartTimes[] array matches the real nodes'
+ * start times
+ */
+ for (int i = 0; i < node.getNbChildren(); i++) {
+ otherNode = treeIO.readNode(node.getChild(i));
+ if (otherNode.getNodeStart() != node.getChildStart(i)) {
+ buf.append(" Expected start time of child node #"
+ + node.getChild(i) + ": " + node.getChildStart(i)
+ + "\n" + " Actual start time of node #"
+ + otherNode.getSequenceNumber() + ": "
+ + otherNode.getNodeStart() + "\n");
+ ret = false;
+ }
+ }
+
+ } catch (ClosedChannelException e) {
+ e.printStackTrace();
+ }
+
+ if (!ret) {
+ System.out.println("");
+ System.out.println("SHT: Integrity check failed for node #"
+ + node.getSequenceNumber() + ":");
+ System.out.println(buf.toString());
+ }
+ return ret;
+ }
+
+ /**
+ * Check the integrity of all the nodes in the tree. Calls
+ * {@link #checkNodeIntegrity} for every node in the tree.
+ */
+ public void checkIntegrity() {
+ try {
+ for (int i = 0; i < nodeCount; i++) {
+ checkNodeIntegrity(treeIO.readNode(i));
+ }
+ } catch (ClosedChannelException e) {
+ }
+ }
+
+ /* Only used for debugging, shouldn't be externalized */
+ @SuppressWarnings("nls")
+ @Override
+ public String toString() {
+ return "Information on the current tree:\n\n" + "Blocksize: "
+ + config.getBlockSize() + "\n" + "Max nb. of children per node: "
+ + config.getMaxChildren() + "\n" + "Number of nodes: " + nodeCount
+ + "\n" + "Depth of the tree: " + latestBranch.size() + "\n"
+ + "Size of the treefile: " + this.getFileSize() + "\n"
+ + "Root node has sequence number: "
+ + latestBranch.get(0).getSequenceNumber() + "\n"
+ + "'Latest leaf' has sequence number: "
+ + latestBranch.get(latestBranch.size() - 1).getSequenceNumber();
+ }
+
+ /**
+ * Start at currentNode and print the contents of all its children, in
+ * pre-order. Give the root node in parameter to visit the whole tree, and
+ * have a nice overview.
+ */
+ /* Only used for debugging, shouldn't be externalized */
+ @SuppressWarnings("nls")
+ private void preOrderPrint(PrintWriter writer, boolean printIntervals,
+ HTNode currentNode, int curDepth) {
+
+ writer.println(currentNode.toString());
+ if (printIntervals) {
+ currentNode.debugPrintIntervals(writer);
+ }
+
+ switch (currentNode.getNodeType()) {
+ case LEAF:
+ /* Stop if it's the leaf node */
+ return;
+
+ case CORE:
+ try {
+ final CoreNode node = (CoreNode) currentNode;
+ /* Print the extensions, if any */
+ int extension = node.getExtensionSequenceNumber();
+ while (extension != -1) {
+ HTNode nextNode = treeIO.readNode(extension);
+ preOrderPrint(writer, printIntervals, nextNode, curDepth);
+ }
+
+ /* Print the child nodes */
+ for (int i = 0; i < node.getNbChildren(); i++) {
+ HTNode nextNode = treeIO.readNode(node.getChild(i));
+ for (int j = 0; j < curDepth; j++) {
+ writer.print(" ");
+ }
+ writer.print("+-");
+ preOrderPrint(writer, printIntervals, nextNode, curDepth + 1);
+ }
+ } catch (ClosedChannelException e) {
+ Activator.getDefault().logError(e.getMessage());
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Print out the full tree for debugging purposes
+ *
+ * @param writer
+ * PrintWriter in which to write the output
+ * @param printIntervals
+ * Flag to enable full output of the interval information
+ */
+ public void debugPrintFullTree(PrintWriter writer, boolean printIntervals) {
+ /* Only used for debugging, shouldn't be externalized */
+
+ this.preOrderPrint(writer, false, latestBranch.get(0), 0);
+
+ if (printIntervals) {
+ writer.println("\nDetails of intervals:"); //$NON-NLS-1$
+ this.preOrderPrint(writer, true, latestBranch.get(0), 0);
+ }
+ writer.println('\n');
+ }
+
+}