Merge Generic State System core part
authorAlexandre Montplaisir <alexandre.montplaisir@gmail.com>
Wed, 14 Mar 2012 21:26:03 +0000 (17:26 -0400)
committerFrancois Chouinard <fchouinard@gmail.com>
Mon, 19 Mar 2012 21:43:57 +0000 (17:43 -0400)
27 files changed:
org.eclipse.linuxtools.tmf.core/META-INF/MANIFEST.MF
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/interval/ITmfStateInterval.java [new file with mode: 0644]
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/interval/TmfStateInterval.java [new file with mode: 0644]
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/Attribute.java [new file with mode: 0644]
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/AttributeNotFoundException.java [new file with mode: 0644]
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/AttributeTree.java [new file with mode: 0644]
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/StateHistorySystem.java [new file with mode: 0644]
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/StateSystem.java [new file with mode: 0644]
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/TimeRangeException.java [new file with mode: 0644]
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/TransientState.java [new file with mode: 0644]
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/backend/historytree/CoreNode.java [new file with mode: 0644]
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/backend/historytree/HTConfig.java [new file with mode: 0644]
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/backend/historytree/HTInterval.java [new file with mode: 0644]
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/backend/historytree/HTNode.java [new file with mode: 0644]
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/backend/historytree/HT_IO.java [new file with mode: 0644]
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/backend/historytree/HistoryTree.java [new file with mode: 0644]
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/backend/historytree/HistoryTreeBackend.java [new file with mode: 0644]
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/backend/historytree/ThreadedHistoryTreeBackend.java [new file with mode: 0644]
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/helpers/HistoryBuilder.java [new file with mode: 0644]
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/helpers/IStateChangeInput.java [new file with mode: 0644]
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/helpers/IStateHistoryBackend.java [new file with mode: 0644]
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statevalue/ITmfStateValue.java [new file with mode: 0644]
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statevalue/IntegerStateValue.java [new file with mode: 0644]
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statevalue/NullStateValue.java [new file with mode: 0644]
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statevalue/StateValueTypeException.java [new file with mode: 0644]
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statevalue/StringStateValue.java [new file with mode: 0644]
org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statevalue/TmfStateValue.java [new file with mode: 0644]

index 41fe314603a2e335eb3930329bcb7eeb86f79d49..bf6356a56b9e8a6703b70cacd426d6a5ba7e383a 100644 (file)
@@ -18,10 +18,15 @@ Export-Package: org.eclipse.linuxtools.tmf.core,
  org.eclipse.linuxtools.tmf.core.filter,
  org.eclipse.linuxtools.tmf.core.filter.model,
  org.eclipse.linuxtools.tmf.core.filter.xml,
+ org.eclipse.linuxtools.tmf.core.interval,
  org.eclipse.linuxtools.tmf.core.io,
  org.eclipse.linuxtools.tmf.core.parser,
  org.eclipse.linuxtools.tmf.core.request,
  org.eclipse.linuxtools.tmf.core.signal,
+ org.eclipse.linuxtools.tmf.core.statesystem,
+ org.eclipse.linuxtools.tmf.core.statesystem.backend.historytree,
+ org.eclipse.linuxtools.tmf.core.statesystem.helpers,
+ org.eclipse.linuxtools.tmf.core.statevalue,
  org.eclipse.linuxtools.tmf.core.trace,
  org.eclipse.linuxtools.tmf.core.uml2sd,
  org.eclipse.linuxtools.tmf.core.util
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/interval/ITmfStateInterval.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/interval/ITmfStateInterval.java
new file mode 100644 (file)
index 0000000..397f1af
--- /dev/null
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * 
+ * 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
+ ******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.interval;
+
+import org.eclipse.linuxtools.tmf.core.statevalue.ITmfStateValue;
+
+/**
+ * This is the basic interface for accessing state intervals. See
+ * StateInterval.java for a basic implementation.
+ * 
+ * A StateInterval is meant to be immutable. All implementing (non-abstract)
+ * classes should ideally be marked as 'final'.
+ * 
+ * @author alexmont
+ * 
+ */
+public interface ITmfStateInterval {
+
+    /**
+     * Retrieve the start time of the interval
+     * 
+     * @return
+     */
+    public long getStartTime();
+
+    /**
+     * Retrieve the end time of the interval
+     * 
+     * @return
+     */
+    public long getEndTime();
+
+    /**
+     * Retrieve the quark of the attribute this state interval refers to
+     * 
+     * @return
+     */
+    public int getAttribute();
+
+    /**
+     * Retrieve the state value represented by this interval
+     * 
+     * @return
+     */
+    public ITmfStateValue getStateValue();
+
+    /**
+     * Test if this interval intersects another timestamp, inclusively.
+     * 
+     * @param timestamp
+     *            The target timestamp
+     * @return True if the interval and timestamp intersect, false if they don't
+     */
+    public boolean intersects(long timestamp);
+}
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/interval/TmfStateInterval.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/interval/TmfStateInterval.java
new file mode 100644 (file)
index 0000000..e7ab298
--- /dev/null
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * Copyright (c) 2010, 2011 École Polytechnique de Montréal
+ * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
+ * 
+ * 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
+ * 
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.interval;
+
+import org.eclipse.linuxtools.tmf.core.statevalue.ITmfStateValue;
+
+/**
+ * The StateInterval represents the "state" a particular attribute was in, at a
+ * given time. It is the main object being returned from queries to the state
+ * system.
+ * 
+
+ * 
+ * @author alexmont
+ * 
+ */
+public final class TmfStateInterval implements ITmfStateInterval {
+
+    private final long start;
+    private final long end;
+    private final int attribute;
+    private final ITmfStateValue sv;
+
+    /**
+     * Construct an interval from its given parameters
+     * 
+     * @param start
+     * @param end
+     * @param attribute
+     * @param sv
+     */
+    public TmfStateInterval(long start, long end, int attribute,
+            ITmfStateValue sv) {
+        this.start = start;
+        this.end = end;
+        this.attribute = attribute;
+        this.sv = sv;
+    }
+
+    @Override
+    public long getStartTime() {
+        return start;
+    }
+
+    @Override
+    public long getEndTime() {
+        return end;
+    }
+
+    @Override
+    public int getAttribute() {
+        return attribute;
+    }
+
+    @Override
+    public ITmfStateValue getStateValue() {
+        return sv;
+    }
+
+    @Override
+    public boolean intersects(long timestamp) {
+        if (start <= timestamp) {
+            if (end >= timestamp) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        /* Only used for debugging */
+        StringBuffer buf = new StringBuffer(start + " to "); //$NON-NLS-1$
+        buf.append(end + ' ');
+        buf.append(String.format("key = %4d, ", attribute)); //$NON-NLS-1$
+        buf.append("value = " + sv.toString()); //$NON-NLS-1$
+        return buf.toString();
+    }
+
+}
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/Attribute.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/Attribute.java
new file mode 100644 (file)
index 0000000..cefc3ec
--- /dev/null
@@ -0,0 +1,236 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * Copyright (c) 2010, 2011 École Polytechnique de Montréal
+ * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
+ * 
+ * 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
+ * 
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.statesystem;
+
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Vector;
+
+/**
+ * An Attribute is a "node" in the Attribute Tree. It represents a smallest
+ * unit of the model which can be in a particular state at a given time.
+ * 
+ * It is abstract, as different implementations can provide different ways to
+ * access sub-attributes
+ * 
+ * @author alexmont
+ * 
+ */
+abstract class Attribute {
+
+    private final Attribute parent;
+    private final String name;
+    private final int quark;
+    protected final Vector<Attribute> subAttributes;
+
+    /**
+     * Constructor
+     */
+    Attribute(Attribute parent, String name, int quark) {
+        this.parent = parent;
+        this.quark = quark;
+        this.name = name;
+        this.subAttributes = new Vector<Attribute>();
+    }
+
+    /**
+     * @name Accessors
+     */
+
+    int getQuark() {
+        return quark;
+    }
+
+    Attribute getParent() {
+        return parent;
+    }
+
+    List<Attribute> getSubAttributesList() {
+        return subAttributes;
+    }
+
+    String getName() {
+        return name;
+    }
+
+    /**
+     * Get the matching quark for a given path-of-strings
+     * 
+     * @param path
+     *            The path we are looking for, *relative to this node*.
+     * @return The matching quark, or -1 if that attribute does not exist.
+     */
+    int getSubAttributeQuark(String... path) {
+        return this.getSubAttributeQuark(path, 0);
+    }
+
+    /**
+     * Other method to search through the attribute tree, but instead of
+     * returning the matching quark we return the AttributeTreeNode object
+     * itself. It can then be used as new "root node" for faster queries on the
+     * tree.
+     * 
+     * @param path
+     *            The target path, *relative to this node*
+     * @return The Node object matching the last element in the path, or "null"
+     *         if that attribute does not exist.
+     */
+    Attribute getSubAttributeNode(String... path) {
+        return this.getSubAttributeNode(path, 0);
+    }
+
+    /**
+     * "Inner" part of the previous public method, which is used recursively. To
+     * avoid having to copy sub-arrays to pass down, we just track where we are
+     * at with the index parameter. It uses getSubAttributeNode(), whose
+     * implementation is left to the derived classes.
+     */
+    private int getSubAttributeQuark(String[] path, int index) {
+        Attribute targetNode = this.getSubAttributeNode(path, index);
+        if (targetNode == null) {
+            return -1;
+        }
+        return targetNode.getQuark();
+    }
+
+    /* The methods how to access children are left to derived classes */
+    abstract void addSubAttribute(Attribute newSubAttribute);
+    abstract Attribute getSubAttributeNode(String[] path, int index);
+
+    /**
+     * Return a String array composed of the full (absolute) path representing
+     * this attribute
+     * 
+     * @return
+     */
+    String[] getFullAttribute() {
+        LinkedList<String> list = new LinkedList<String>();
+        Attribute curNode = this;
+
+        /* Add recursive parents to the list, but stop at the root node */
+        while (curNode.getParent() != null) {
+            list.add(curNode.getName());
+            curNode = curNode.getParent();
+        }
+
+        Collections.reverse(list);
+
+        return list.toArray(new String[0]);
+    }
+
+    /**
+     * Return the absolute path of this attribute, as a single slash-separated
+     * String.
+     * 
+     * @return
+     */
+    String getFullAttributeName() {
+        String[] array;
+        String ret = ""; //$NON-NLS-1$
+
+        array = this.getFullAttribute();
+        for (int i = 0; i < array.length - 1; i++) {
+            ret += array[i] + '/';
+        }
+        ret += array[array.length - 1];
+        return ret;
+    }
+
+    @Override
+    public String toString() {
+        return getFullAttributeName() + " (" + quark + ')'; //$NON-NLS-1$
+    }
+
+    private int curDepth;
+
+    private void attributeNodeToString(PrintWriter writer, Attribute currentNode) {
+        int j;
+
+        writer.println(currentNode.getName() + " (" + currentNode.quark + ')'); //$NON-NLS-1$
+        curDepth++;
+
+        for (Attribute nextNode : currentNode.getSubAttributesList()) {
+            /* Skip printing 'null' entries */
+            if (nextNode == null) {
+                continue;
+            }
+            for (j = 0; j < curDepth - 1; j++) {
+                writer.print("  "); //$NON-NLS-1$
+            }
+            writer.print("  "); //$NON-NLS-1$
+            attributeNodeToString(writer, nextNode);
+        }
+        curDepth--;
+        return;
+    }
+
+    void debugPrint(PrintWriter writer) {
+        /* Only used for debugging, shouldn't be externalized */
+        writer.println("------------------------------"); //$NON-NLS-1$
+        writer.println("Attribute tree: (quark)\n"); //$NON-NLS-1$
+        curDepth = 0;
+        attributeNodeToString(writer, this);
+        writer.print('\n');
+    }
+}
+
+/**
+ * This is the basic implementation, where sub-attributes names can be composed
+ * of any alphanumeric characters, and are stored as Strings. A HashMap is used
+ * to access them.
+ * 
+ * @author alexmont
+ * 
+ */
+final class AlphaNumAttribute extends Attribute {
+
+    private HashMap<String, Integer> subAttributesMap;
+
+    AlphaNumAttribute(Attribute parent, String name, int quark) {
+        super(parent, name, quark);
+        this.subAttributesMap = new HashMap<String, Integer>();
+    }
+
+    @Override
+    synchronized void addSubAttribute(Attribute newSubAttribute) {
+        assert (newSubAttribute != null);
+        assert (newSubAttribute.getName() != null);
+        /* This should catch buggy state changing statements */
+        assert (!newSubAttribute.getName().equals(this.getName()));
+
+        subAttributesMap.put(newSubAttribute.getName(), subAttributes.size());
+        subAttributes.add(newSubAttribute);
+    }
+
+    @Override
+    protected synchronized Attribute getSubAttributeNode(String[] path,
+            int index) {
+        Integer indexOfNextNode = subAttributesMap.get(path[index]);
+        Attribute nextNode;
+
+        if (indexOfNextNode == null) {
+            /* We don't have the expected child => the attribute does not exist */
+            return null;
+        }
+        if (index == path.length - 1) {
+            /* It's our job to process this request */
+            return subAttributes.get(indexOfNextNode);
+        }
+
+        nextNode = subAttributes.get(indexOfNextNode);
+        return nextNode.getSubAttributeNode(path, index + 1);
+    }
+}
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/AttributeNotFoundException.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/AttributeNotFoundException.java
new file mode 100644 (file)
index 0000000..a96af7f
--- /dev/null
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * Copyright (c) 2010, 2011 École Polytechnique de Montréal
+ * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
+ * 
+ * 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
+ * 
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.statesystem;
+
+/**
+ * This exception gets thrown when the user tries to access an attribute which
+ * doesn't exist in the system, of if the quark is simply invalid (ie, < 0).
+ * 
+ * @author alexmont
+ *
+ */
+public class AttributeNotFoundException extends Exception {
+
+    /**
+     * 
+     */
+    private static final long serialVersionUID = 7964275803369706145L;
+
+}
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/AttributeTree.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/AttributeTree.java
new file mode 100644 (file)
index 0000000..7cbb7a0
--- /dev/null
@@ -0,0 +1,304 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * Copyright (c) 2010, 2011 École Polytechnique de Montréal
+ * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
+ * 
+ * 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
+ * 
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.statesystem;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The Attribute Tree is the /proc-like filesystem used to organize attributes.
+ * Each node of this tree is both like a file and a directory in the
+ * "file system".
+ * 
+ * @author alexmont
+ * 
+ */
+final class AttributeTree {
+
+    /* "Magic number" for attribute tree files or file sections */
+    private final static int ATTRIB_TREE_MAGIC_NUMBER = 0x06EC3671;
+
+    private final StateSystem ss;
+    private final List<Attribute> attributeList;
+    private final Attribute attributeTreeRoot;
+
+    /**
+     * Standard constructor, create a new empty Attribute Tree
+     * 
+     * @param ss
+     *            The StateSystem to which this AT is attached
+     */
+    AttributeTree(StateSystem ss) {
+        this.ss = ss;
+        this.attributeList = Collections.synchronizedList(new ArrayList<Attribute>());
+        this.attributeTreeRoot = new AlphaNumAttribute(null, "root", -1); //$NON-NLS-1$
+    }
+
+    /**
+     * "Existing file" constructor Builds a attribute tree from a "mapping file"
+     * or mapping section previously saved somewhere.
+     * 
+     * @param ss
+     *            StateSystem to which this AT is attached
+     * @param fis
+     *            File stream where to read the AT information. Make sure it's
+     *            seeked at the right place!
+     * @throws IOException
+     */
+    AttributeTree(StateSystem ss, FileInputStream fis) throws IOException {
+        this(ss);
+        DataInputStream in = new DataInputStream(new BufferedInputStream(fis));
+
+        /* Message for exceptions, shouldn't be externalized */
+        final String errorMessage = "The attribute tree file section is either invalid or corrupted."; //$NON-NLS-1$
+        
+        ArrayList<String[]> list = new ArrayList<String[]>();
+        byte[] curByteArray;
+        String curFullString;
+        String[] curStringArray;
+        int res, remain, size;
+        int expectedSize = 0;
+        int total = 0;
+
+        /* Read the header of the Attribute Tree file (or file section) */
+        res = in.readInt(); /* Magic number */
+        if (res != ATTRIB_TREE_MAGIC_NUMBER) {
+            throw new IOException(errorMessage);
+        }
+
+        /* Expected size of the section */
+        expectedSize = in.readInt();
+        if (expectedSize <= 12) {
+            throw new IOException(errorMessage);
+        }
+
+        /* How many entries we have to read */
+        remain = in.readInt();
+        total += 12;
+
+        /* Read each entry */
+        for (; remain > 0; remain--) {
+            /* Read the first byte = the size of the entry */
+            size = in.readByte();
+            curByteArray = new byte[size];
+            in.read(curByteArray);
+
+            /*
+             * Go buffer -> byteArray -> String -> String[] -> insert in list.
+             * bleh
+             */
+            curFullString = new String(curByteArray);
+            curStringArray = curFullString.split("/"); //$NON-NLS-1$
+            list.add(curStringArray);
+
+            /* Read the 0'ed confirmation byte */
+            res = in.readByte();
+            if (res != 0) {
+                throw new IOException(errorMessage);
+            }
+            total += curByteArray.length + 2;
+        }
+
+        if (total != expectedSize) {
+            throw new IOException(errorMessage);
+        }
+
+        /*
+         * Now we have 'list', the ArrayList of String arrays representing all
+         * the attributes. Simply create attributes the normal way from them.
+         */
+        for (String[] attrib : list) {
+            this.getQuarkAndAdd(-1, attrib);
+        }
+    }
+
+    /**
+     * Tell the Attribute Tree to write itself somewhere. The passed
+     * FileOutputStream defines where (which file/position).
+     * 
+     * @param fos
+     *            Where to write. Make sure it's seeked at the right position
+     *            you want.
+     * @return The total number of bytes written.
+     */
+    int writeSelf(File file, long pos) {
+        RandomAccessFile raf;
+        int total = 0;
+        byte[] curByteArray;
+
+        try {
+            raf = new RandomAccessFile(file, "rw"); //$NON-NLS-1$
+            raf.seek(pos);
+
+            /* Write the almost-magic number */
+            raf.writeInt(ATTRIB_TREE_MAGIC_NUMBER);
+
+            /* Placeholder for the total size of the section... */
+            raf.writeInt(-8000);
+
+            /* Write the number of entries */
+            raf.writeInt(this.attributeList.size());
+            total += 12;
+
+            /* Write the attributes themselves */
+            for (Attribute entry : this.attributeList) {
+                curByteArray = entry.getFullAttributeName().getBytes();
+                if (curByteArray.length > Byte.MAX_VALUE) {
+                    throw new IOException("Attribute with name \"" //$NON-NLS-1$
+                            + Arrays.toString(curByteArray) + "\" is too long."); //$NON-NLS-1$
+                }
+                /* Write the first byte = size of the array */
+                raf.writeByte((byte) curByteArray.length);
+
+                /* Write the array itself */
+                raf.write(curByteArray);
+
+                /* Write the 0'ed byte */
+                raf.writeByte((byte) 0);
+
+                total += curByteArray.length + 2;
+            }
+
+            /* Now go back and write the actual size of this section */
+            raf.seek(pos + 4);
+            raf.writeInt(total);
+
+            raf.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return total;
+    }
+
+    /**
+     * Return the number of attributes this system as seen so far. Note that
+     * this also equals the integer value (quark) the next added attribute will
+     * have.
+     * 
+     * @return
+     */
+    int getNbAttributes() {
+        return attributeList.size();
+    }
+
+    /**
+     * This is the version to specifically add missing attributes.
+     * 
+     * If 'numericalNode' is true, all the new attributes created will be of
+     * type 'NumericalNode' instead of 'AlphaNumNode'. Be careful with this, if
+     * you do not want ALL added attributes to be numerical, call this function
+     * first with 'false' to create the parent nodes, then call it again to make
+     * sure only the final node is numerical.
+     * 
+     * @throws AttributeNotFoundException
+     */
+    int getQuarkDontAdd(int startingNodeQuark, String... subPath)
+            throws AttributeNotFoundException {
+        assert (subPath != null && subPath.length > 0);
+        assert (startingNodeQuark >= -1);
+
+        Attribute prevNode;
+
+        /* Get the "starting node" */
+        if (startingNodeQuark == -1) {
+            prevNode = attributeTreeRoot;
+        } else {
+            prevNode = attributeList.get(startingNodeQuark);
+        }
+
+        int knownQuark = prevNode.getSubAttributeQuark(subPath);
+        if (knownQuark == -1) {
+            /*
+             * The attribute doesn't exist, but we have been specified to NOT
+             * add any new attributes.
+             */
+            throw new AttributeNotFoundException();
+        }
+        /*
+         * The attribute was already existing, return the quark of that
+         * attribute
+         */
+        return knownQuark;
+    }
+
+    // FIXME synchronized here is probably quite costly... maybe only locking
+    // the "for" would be enough?
+    synchronized int getQuarkAndAdd(int startingNodeQuark, String... subPath) {
+        assert (subPath != null && subPath.length > 0);
+        assert (startingNodeQuark >= -1);
+
+        Attribute nextNode = null;
+        Attribute prevNode;
+
+        /* Get the "starting node" */
+        if (startingNodeQuark == -1) {
+            prevNode = attributeTreeRoot;
+        } else {
+            prevNode = attributeList.get(startingNodeQuark);
+        }
+
+        int knownQuark = prevNode.getSubAttributeQuark(subPath);
+        if (knownQuark == -1) {
+            /*
+             * The attribute was not in the table previously, and we want to add
+             * it
+             */
+            for (String curDirectory : subPath) {
+                nextNode = prevNode.getSubAttributeNode(curDirectory);
+                if (nextNode == null) {
+                    /* This is where we need to start adding */
+                    nextNode = new AlphaNumAttribute(prevNode, curDirectory,
+                            attributeList.size());
+                    prevNode.addSubAttribute(nextNode);
+                    attributeList.add(nextNode);
+                    ss.transState.addEmptyEntry();
+                }
+                prevNode = nextNode;
+            }
+            return attributeList.size() - 1;
+        }
+        /*
+         * The attribute was already existing, return the quark of that
+         * attribute
+         */
+        return knownQuark;
+    }
+
+    int getSubAttributesCount(int quark) {
+        return attributeList.get(quark).getSubAttributesList().size();
+    }
+
+    ArrayList<Integer> getSubAttributes(int attributeQuark) {
+        ArrayList<Integer> listOfChildren = new ArrayList<Integer>();
+
+        for (Attribute childNode : attributeList.get(attributeQuark).getSubAttributesList()) {
+            listOfChildren.add(childNode.getQuark());
+        }
+        return listOfChildren;
+    }
+
+    String getFullAttributeName(int quark) {
+        if (quark >= attributeList.size() || quark < 0) {
+            return null;
+        }
+        return attributeList.get(quark).getFullAttributeName();
+    }
+
+    void debugPrint(PrintWriter writer) {
+        attributeTreeRoot.debugPrint(writer);
+    }
+
+}
\ No newline at end of file
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/StateHistorySystem.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/StateHistorySystem.java
new file mode 100644 (file)
index 0000000..3f77430
--- /dev/null
@@ -0,0 +1,277 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * Copyright (c) 2010, 2011 École Polytechnique de Montréal
+ * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
+ * 
+ * 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
+ * 
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.statesystem;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.linuxtools.tmf.core.interval.ITmfStateInterval;
+import org.eclipse.linuxtools.tmf.core.statesystem.helpers.IStateHistoryBackend;
+
+/**
+ * This is the extension of the StateSystem, which will save the state intervals
+ * that are created from the transient state (instead of discarding them, like a
+ * simple StateSystem would do).
+ * 
+ * This allows the user to then run queries at past timestamps.
+ * 
+ * DON'T FORGET to call .closeHistory() when you are done inserting intervals,
+ * or the storage backend will have no way of knowing it can close and write
+ * itself to disk, and its thread will keep running.
+ * 
+ * @author alexmont
+ * 
+ */
+public class StateHistorySystem extends StateSystem {
+
+    /*
+     * Inherited from StateSystem
+     * 
+     * protected ArrayList<AttributeTreeNode> attributeList; protected
+     * TransientState transState; protected CurrentState curState;
+     */
+
+    private final IStateHistoryBackend backend;
+
+    /*
+     * This the container that will hold the result of full queries obtained
+     * with loadStateAtTime()
+     */
+    private ArrayList<ITmfStateInterval> currentStateInfo;
+
+    /**
+     * General constructor
+     * 
+     * @param backend
+     *            The "state history storage" backend to use.
+     * @param newFile
+     *            Put true if this is a new history started from scratch. It is
+     *            used to tell the state system where to get its attribute tree.
+     * @throws IOException
+     */
+    public StateHistorySystem(IStateHistoryBackend backend, boolean newFile)
+            throws IOException {
+        this.backend = backend;
+        transState = new TransientState(backend);
+        currentStateInfo = new ArrayList<ITmfStateInterval>();
+
+        if (newFile) {
+            attributeTree = new AttributeTree(this);
+        } else {
+            /* We're opening an existing file */
+            FileInputStream attributeTreeInput = backend.supplyAttributeTreeReader();
+            this.attributeTree = new AttributeTree(this, attributeTreeInput);
+            transState.setInactive();
+        }
+    }
+
+    public IStateHistoryBackend getHistoryBackend() {
+        return backend;
+    }
+
+    /**
+     * Method to close off the History Provider. This happens for example when
+     * we are done reading an off-line trace. First we close the TransientState,
+     * commit it to the Provider, mark it as inactive, then we write the
+     * Attribute Tree somewhere so we can reopen it later.
+     * 
+     * @param endTime
+     *            The requested End Time of the history, since it could be
+     *            bigger than the timestamp of the last event or state change we
+     *            have seen. All "ongoing" states will be extended until this
+     *            'endTime'.
+     * @throws TimeRangeException
+     *             If the passed endTime doesn't make sense (for example, if
+     *             it's earlier than the latest time) and the backend doesn't
+     *             know how to handle it.
+     */
+    public void closeHistory(long endTime) throws TimeRangeException {
+        File attributeTreeFile;
+        long attributeTreeFilePos;
+        long realEndTime = endTime;
+
+        if (realEndTime < backend.getEndTime()) {
+            /*
+             * This can happen (empty nodes pushing the border further, etc.)
+             * but shouldn't be too big of a deal.
+             */
+            realEndTime = backend.getEndTime();
+        }
+        transState.closeTransientState(realEndTime);
+        backend.finishedBuilding(realEndTime);
+
+        attributeTreeFile = backend.supplyAttributeTreeWriterFile();
+        attributeTreeFilePos = backend.supplyAttributeTreeWriterFilePosition();
+        if (attributeTreeFile != null) {
+            /*
+             * If null was returned, we simply won't save the attribute tree,
+             * too bad!
+             */
+            attributeTree.writeSelf(attributeTreeFile, attributeTreeFilePos);
+        }
+    }
+
+    /**
+     * @name External query methods
+     */
+
+    /**
+     * Load the state information at time 't' as the Current State. You can then
+     * use the queryState() method to query the value of different attributes,
+     * as they were at time 't'.
+     * 
+     * On average if you need around 10 or more queries for the same timestamps,
+     * use this. If you need less than 10 (for example, running many queries for
+     * the same attributes but at different timestamps), you might be better
+     * using the querySingleState() methods instead.
+     * 
+     * @param t
+     *            We will recreate the state information to what it was at time
+     *            t.
+     * @throws TimeRangeException
+     */
+    public synchronized void loadStateAtTime(long t) throws TimeRangeException {
+        /* Empty the currentStateInfo */
+        currentStateInfo = new ArrayList<ITmfStateInterval>(
+                attributeTree.getNbAttributes());
+        for (int i = 0; i < attributeTree.getNbAttributes(); i++) {
+            currentStateInfo.add(null);
+        }
+
+        backend.doQuery(currentStateInfo, t);
+
+        if (transState.isActive()) {
+            transState.doQuery(currentStateInfo, t);
+        }
+        // We should have previously inserted an interval for every attribute
+        // for all possible timestamps (and those could contain 'nullValues').
+        // There should be no 'null' objects at this point.
+        for (ITmfStateInterval interval : currentStateInfo) {
+            assert (interval != null);
+        }
+    }
+
+    /**
+     * Once we have set up the "current state" using loadStateAtTime(), we can
+     * now run queries to get the state of individual attributes at the
+     * previously loaded timestamp.
+     * 
+     * @param attributeQuark
+     *            The quark of attribute for which we want the state.
+     * @return The StateInterval object matching this timestamp/attribute pair.
+     * @throws AttributeNotFoundException
+     */
+    public ITmfStateInterval queryState(int attributeQuark) {
+        return currentStateInfo.get(attributeQuark);
+    }
+
+    /**
+     * Alternative, singular version of the "queryState" method. This one does
+     * not update the whole stateInfo vector, like loadStateAtTimes does. It
+     * only searches for one specific entry in the state history.
+     * 
+     * It should be used when you only want very few entries, instead of the
+     * whole state (or many entries, but all at different timestamps). If you do
+     * request many entries all at the same time, you should use the
+     * conventional loadStateAtTime() + queryState() method.
+     * 
+     * @param t
+     *            The timestamp at which we want the state
+     * @param attributeQuark
+     *            Which attribute we want to get the state of
+     * @return The StateInterval representing the state
+     * @throws TimeRangeException
+     * @throws AttributeNotFoundException
+     */
+    public ITmfStateInterval querySingleState(long t, int attributeQuark)
+            throws AttributeNotFoundException, TimeRangeException {
+        ITmfStateInterval ret;
+
+        if (transState.hasInfoAboutStateOf(t, attributeQuark)) {
+            ret = transState.getOngoingInterval(attributeQuark);
+        } else {
+            ret = backend.doSingularQuery(t, attributeQuark);
+        }
+        assert (ret != null);
+        return ret;
+    }
+
+    /**
+     * Return a list of state intervals, containing the "history" of a given
+     * attribute between timestamps t1 and t2. The list will be ordered by
+     * ascending time.
+     * 
+     * @param attributeQuark
+     *            Which attribute this query is interested in
+     * @param t1
+     *            Start time of the range query
+     * @param t2
+     *            End time of the query
+     * @return The List of state intervals that happened between t1 and t2
+     * @throws TimeRangeException
+     * @throws AttributeNotFoundException
+     */
+    public List<ITmfStateInterval> queryHistoryRange(int attributeQuark, long t1,
+            long t2) throws TimeRangeException, AttributeNotFoundException {
+
+        List<ITmfStateInterval> intervals = new ArrayList<ITmfStateInterval>();
+        ITmfStateInterval currentInterval;
+        long ts;
+
+        /* Get the initial state at time T1 */
+        currentInterval = querySingleState(t1, attributeQuark);
+        intervals.add(currentInterval);
+
+        /* Get the following state changes */
+        ts = currentInterval.getEndTime();
+        while (ts != -1 && ts <= t2) {
+            ts++; /* To "jump over" to the next state in the history */
+            currentInterval = querySingleState(ts, attributeQuark);
+            intervals.add(currentInterval);
+            ts = currentInterval.getEndTime();
+        }
+        return intervals;
+    }
+
+    /**
+     * @name Debugging methods
+     */
+
+    /**
+     * Print out the contents of the inner structures to the selected
+     * PrintWriter.
+     */
+    @Override
+    public void debugPrint(PrintWriter writer) {
+        /* Only used for debugging, shouldn't be externalized */
+        writer.println("------------------------------"); //$NON-NLS-1$
+        writer.println("Current State Info vector:\n"); //$NON-NLS-1$
+        for (int i = 0; i < currentStateInfo.size(); i++) {
+            writer.print(i + "\t\t"); //$NON-NLS-1$
+            if (currentStateInfo.get(i) == null) {
+                writer.println("null"); //$NON-NLS-1$
+            } else {
+                writer.println(currentStateInfo.get(i).toString());
+            }
+        }
+        writer.println('\n');
+
+        /* Print the other inner containers */
+        super.debugPrint(writer);
+        backend.debugPrint(writer);
+    }
+}
\ No newline at end of file
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/StateSystem.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/StateSystem.java
new file mode 100644 (file)
index 0000000..0d24cb7
--- /dev/null
@@ -0,0 +1,410 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * Copyright (c) 2010, 2011 École Polytechnique de Montréal
+ * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
+ * 
+ * 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
+ * 
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.statesystem;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+import org.eclipse.linuxtools.tmf.core.statevalue.ITmfStateValue;
+import org.eclipse.linuxtools.tmf.core.statevalue.StateValueTypeException;
+import org.eclipse.linuxtools.tmf.core.statevalue.TmfStateValue;
+
+/**
+ * This is the base class for the StateHistorySystem. It contains all the
+ * current-state-updating methods.
+ * 
+ * It's not abstract, as it can be used by itself: in this case, no History tree
+ * will be built underneath (no information will be saved to disk) and it will
+ * only be able to respond to queries to the current, latest time.
+ * 
+ * @author alexmont
+ * 
+ */
+public class StateSystem {
+
+    /* References to the inner structures */
+    protected AttributeTree attributeTree;
+    protected TransientState transState;
+
+    /**
+     * Constructor. No configuration needed!
+     */
+    public StateSystem() {
+        attributeTree = new AttributeTree(this);
+
+        /* This will tell the builder to discard the intervals */
+        transState = new TransientState(null);
+    }
+
+    /**
+     * @name Quark-retrieving methods
+     */
+
+    /**
+     * Basic quark-retrieving method. Pass an attribute in parameter as an array
+     * of strings, the matching quark will be returned.
+     * 
+     * This version will NOT create any new attributes. If an invalid attribute
+     * is requested, an exception will be thrown. This should ideally be used
+     * for doing read-only operations on the system, like queries for example.
+     * 
+     * @param attribute
+     *            Attribute given as its full path in the Attribute Tree
+     * @return The quark of the requested attribute, if it existed.
+     * @throws AttributeNotFoundException
+     *             This exception is thrown if the requested attribute simply
+     *             did not exist in the system.
+     */
+    public int getQuarkAbsolute(String... attribute)
+            throws AttributeNotFoundException {
+        return attributeTree.getQuarkDontAdd(-1, attribute);
+    }
+
+    /**
+     * Basic quark-retrieving method. Pass an attribute in parameter as an array
+     * of strings, the matching quark will be returned.
+     * 
+     * This version WILL create new attributes: if the attribute passed in
+     * parameter is new in the system, it will be added and its new quark will
+     * be returned.
+     * 
+     * @param attribute
+     *            Attribute given as its full path in the Attribute Tree
+     * @return The quark of the attribute (which either existed or just got
+     *         created)
+     */
+    public int getQuarkAbsoluteAndAdd(String... attribute) {
+        return attributeTree.getQuarkAndAdd(-1, attribute);
+    }
+
+    /**
+     * "Relative path" quark-getting method. Instead of specifying a full path,
+     * if you know the path is relative to another attribute for which you
+     * already have the quark, use this for better performance.
+     * 
+     * This is useful for cases where a lot of modifications or queries will
+     * originate from the same branch of the attribute tree : the common part of
+     * the path won't have to be re-hashed for every access.
+     * 
+     * This version will NOT create any new attributes. If an invalid attribute
+     * is requested, an exception will be thrown. This should ideally be used
+     * for doing read-only operations on the system, like queries for example.
+     * 
+     * @param startingNodeQuark
+     *            The quark of the attribute from which 'subPath' originates.
+     * @param subPath
+     *            "Rest" of the path to get to the final attribute
+     * @return The matching quark, if it existed
+     * @throws AttributeNotFoundException
+     */
+    public int getQuarkRelative(int startingNodeQuark, String... subPath)
+            throws AttributeNotFoundException {
+        return attributeTree.getQuarkDontAdd(startingNodeQuark, subPath);
+    }
+
+    /**
+     * "Relative path" quark-getting method. Instead of specifying a full path,
+     * if you know the path is relative to another attribute for which you
+     * already have the quark, use this for better performance.
+     * 
+     * This is useful for cases where a lot of modifications or queries will
+     * originate from the same branch of the attribute tree : the common part of
+     * the path won't have to be re-hashed for every access.
+     * 
+     * This version WILL create new attributes: if the attribute passed in
+     * parameter is new in the system, it will be added and its new quark will
+     * be returned.
+     * 
+     * @param startingNodeQuark
+     *            The quark of the attribute from which 'subPath' originates.
+     * @param subPath
+     *            "Rest" of the path to get to the final attribute
+     * @return The matching quark, either if it's new of just got created.
+     */
+    public int getQuarkRelativeAndAdd(int startingNodeQuark, String... subPath) {
+        return attributeTree.getQuarkAndAdd(startingNodeQuark, subPath);
+    }
+
+    /**
+     * @name External methods related to insertions in the history -
+     */
+
+    /**
+     * Basic attribute modification method, we simply specify a new value, for a
+     * given attribute, effective at the given timestamp.
+     * 
+     * @param t
+     *            Timestamp of the state change
+     * @param value
+     *            The State Value we want to assign to the attribute
+     * @param attributeQuark
+     *            Integer value of the quark corresponding to the attribute we
+     *            want to modify
+     * @throws TimeRangeException
+     *             If the requested time is outside of the trace's range
+     * @throws AttributeNotFoundException
+     *             If the requested attribute quark is invalid
+     */
+    public void modifyAttribute(long t, ITmfStateValue value, int attributeQuark)
+            throws TimeRangeException, AttributeNotFoundException {
+        transState.processStateChange(t, value, attributeQuark);
+    }
+
+    /**
+     * Increment attribute method. Reads the current value of a given integer
+     * attribute (this value is right now in the Transient State), and increment
+     * it by 1. Useful for statistics.
+     * 
+     * @param t
+     *            Timestamp of the state change
+     * @param attributeQuark
+     *            Attribute to increment. If it doesn't exist it will be added,
+     *            with a new value of 1.
+     * @throws StateValueTypeException
+     *             If the attribute already exists but is not of type Integer
+     * @throws TimeRangeException
+     *             If the given timestamp is invalid
+     * @throws AttributeNotFoundException
+     *             If the quark is invalid
+     */
+    public void incrementAttribute(long t, int attributeQuark)
+            throws StateValueTypeException, TimeRangeException,
+            AttributeNotFoundException {
+        int prevValue = queryOngoingState(attributeQuark).unboxInt();
+        /* prevValue should be == 0 if the attribute wasn't existing before */
+        modifyAttribute(t, TmfStateValue.newValueInt(prevValue + 1),
+                attributeQuark);
+    }
+
+    /**
+     * "Push" helper method. This uses the given integer attribute as a stack:
+     * The value of that attribute will represent the stack depth (always >= 1).
+     * Sub-attributes will be created, their base-name will be the position in
+     * the stack (1, 2, etc.) and their value will be the state value 'value'
+     * that was pushed to this position.
+     * 
+     * @param t
+     *            Timestamp of the state change
+     * @param value
+     *            State value to assign to this stack position.
+     * @param attributeQuark
+     *            The base attribute to use as a stack. If it does not exist if
+     *            will be created (with depth = 1)
+     * @throws TimeRangeException
+     *             If the requested timestamp is invalid
+     * @throws AttributeNotFoundException
+     *             If the attribute is invalid
+     * @throws StateValueTypeException
+     *             If the attribute 'attributeQuark' already exists, but is not
+     *             of integer type.
+     */
+    public void pushAttribute(long t, ITmfStateValue value, int attributeQuark)
+            throws TimeRangeException, AttributeNotFoundException,
+            StateValueTypeException {
+        assert (attributeQuark >= 0);
+        Integer stackDepth = 0;
+        int subAttributeQuark;
+        ITmfStateValue previousSV = transState.getOngoingStateValue(attributeQuark);
+
+        if (previousSV.isNull()) {
+            /*
+             * If the StateValue was null, this means this is the first time we
+             * use this attribute. Leave stackDepth at 0.
+             */
+        } else if (previousSV.getType() == 0) {
+            /* Previous value was an integer, all is good, use it */
+            stackDepth = previousSV.unboxInt();
+            if (stackDepth >= 10) {
+                /*
+                 * Limit stackDepth to 10, to avoid having Attribute Trees grow
+                 * out of control due to buggy insertions
+                 */
+                assert (false);
+            }
+        } else {
+            /* Previous state of this attribute was another type? Not good! */
+            assert (false);
+        }
+
+        stackDepth++;
+        subAttributeQuark = getQuarkRelativeAndAdd(attributeQuark,
+                stackDepth.toString());
+
+        modifyAttribute(t, TmfStateValue.newValueInt(stackDepth),
+                attributeQuark);
+        transState.processStateChange(t, value, subAttributeQuark);
+    }
+
+    /**
+     * Antagonist of the pushAttribute(), pops the top-most attribute on the
+     * stack-attribute. If this brings it back to depth = 0, the attribute is
+     * kept with depth = 0. If the value is already 0, or if the attribute
+     * doesn't exist, nothing is done.
+     * 
+     * @param t
+     *            Timestamp of the state change
+     * @param attributeQuark
+     *            Quark of the stack-attribute to pop
+     * @throws AttributeNotFoundException
+     *             If the attribute is invalid
+     * @throws TimeRangeException
+     *             If the timestamp is invalid
+     * @throws StateValueTypeException
+     *             If the target attribute already exists, but its state value
+     *             type is invalid (not an integer)
+     */
+    public void popAttribute(long t, int attributeQuark)
+            throws AttributeNotFoundException, TimeRangeException,
+            StateValueTypeException {
+        assert (attributeQuark >= 0);
+        Integer stackDepth;
+        int subAttributeQuark;
+        ITmfStateValue previousSV = transState.getOngoingStateValue(attributeQuark);
+
+        if (previousSV.isNull()) {
+            /*
+             * If the StateValue was null, this means this is the first time we
+             * use this attribute.
+             */
+            stackDepth = 0;
+        } else {
+            assert (previousSV.getType() == 0);
+            stackDepth = previousSV.unboxInt();
+        }
+
+        if (stackDepth == 0) {
+            /*
+             * Trying to pop an empty stack. This often happens at the start of
+             * traces, for example when we see a syscall_exit, without having
+             * the corresponding syscall_entry in the trace. Just ignore
+             * silently.
+             */
+            return;
+        } else if (stackDepth < 0) {
+            /* This on the other hand should not happen... */
+            assert (false);
+        }
+
+        /* The attribute should already exist... */
+        subAttributeQuark = getQuarkRelative(attributeQuark,
+                stackDepth.toString());
+
+        stackDepth--;
+        modifyAttribute(t, TmfStateValue.newValueInt(stackDepth),
+                attributeQuark);
+        removeAttribute(t, subAttributeQuark);
+    }
+
+    /**
+     * Remove attribute method. Similar to the above modify- methods, with value
+     * = 0 / null, except we will also "nullify" all the sub-contents of the
+     * requested path (a bit like "rm -rf")
+     * 
+     * @param t
+     *            Timestamp of the state change
+     * @param attributeQuark
+     *            Attribute to remove
+     * @throws TimeRangeException
+     *             If the timestamp is invalid
+     * @throws AttributeNotFoundException
+     *             If the quark is invalid
+     */
+    public void removeAttribute(long t, int attributeQuark)
+            throws TimeRangeException, AttributeNotFoundException {
+        assert (attributeQuark >= 0);
+        /* "Nullify our children first, recursively */
+        List<Integer> childAttributes = attributeTree.getSubAttributes(attributeQuark);
+        for (Integer childNodeQuark : childAttributes) {
+            assert (attributeQuark != childNodeQuark);
+            removeAttribute(t, childNodeQuark);
+        }
+        /* Nullify ourselves */
+        transState.processStateChange(t, TmfStateValue.nullValue(),
+                attributeQuark);
+    }
+
+    /**
+     * @name "Current" query/update methods -
+     */
+
+    /**
+     * Returns the current state value we have (in the Transient State) for the
+     * given attribute.
+     * 
+     * This is useful even for a StateHistorySystem, as we are guaranteed it
+     * will only do a memory access and not go look on disk (and we don't even
+     * have to provide a timestamp!)
+     * 
+     * @param attributeQuark
+     *            For which attribute we want the current state
+     * @return The State value that's "current" for this attribute
+     * @throws AttributeNotFoundException
+     *             If the requested attribute is invalid
+     */
+    public ITmfStateValue queryOngoingState(int attributeQuark)
+            throws AttributeNotFoundException {
+        return transState.getOngoingStateValue(attributeQuark);
+    }
+
+    /**
+     * Modify a current "ongoing" state (instead of inserting a state change,
+     * like modifyAttribute() and others).
+     * 
+     * This can be used to update the value of a previous state change, for
+     * example when we get information at the end of the state and not at the
+     * beginning. (return values of system calls, etc.)
+     * 
+     * Note that past states can only be modified while they are still in
+     * memory, so only the "current state" can be updated. Once they get
+     * committed to disk (by inserting a new state change) it becomes too late.
+     * 
+     * @param newValue
+     *            The new value that will overwrite the "current" one.
+     * @param attributeQuark
+     *            For which attribute in the system
+     * @throws AttributeNotFoundException
+     *             If the requested attribute is invalid
+     */
+    public void updateOngoingState(ITmfStateValue newValue, int attributeQuark)
+            throws AttributeNotFoundException {
+        transState.changeOngoingStateValue(attributeQuark, newValue);
+    }
+
+    /**
+     * @name Debugging methods
+     */
+
+    /**
+     * This returns the slash-separated path of an attribute by providing its
+     * quark
+     * 
+     * @param attributeQuark
+     *            The quark of the attribute we want
+     * @return One single string separated with '/', like a filesystem path
+     */
+    public String getFullAttributePath(int attributeQuark) {
+        return attributeTree.getFullAttributeName(attributeQuark);
+    }
+
+    /**
+     * Print out the contents of the inner structures.
+     * 
+     * @param writer
+     *            The PrintWriter in which to print the output
+     */
+    public void debugPrint(PrintWriter writer) {
+        attributeTree.debugPrint(writer);
+        transState.debugPrint(writer);
+    }
+
+}
\ No newline at end of file
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/TimeRangeException.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/TimeRangeException.java
new file mode 100644 (file)
index 0000000..8cde5a9
--- /dev/null
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * Copyright (c) 2010, 2011 École Polytechnique de Montréal
+ * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
+ * 
+ * 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
+ * 
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.statesystem;
+
+/**
+ * Generic exception for when the user specifies an invalid time stamp. Usually
+ * timestamps must be within the range of the trace or state history being
+ * queried.
+ * 
+ * For insertions, it's forbidden to insert new states "in the past" (before where
+ * the cursor is), so this exception is also thrown in that case.
+ * 
+ * @author alexmont
+ *
+ */
+public class TimeRangeException extends Exception {
+
+    /**
+     * 
+     */
+    private static final long serialVersionUID = -4067685227260254532L;
+
+}
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/TransientState.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/TransientState.java
new file mode 100644 (file)
index 0000000..d0a0a7e
--- /dev/null
@@ -0,0 +1,317 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * Copyright (c) 2010, 2011 École Polytechnique de Montréal
+ * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
+ * 
+ * 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
+ * 
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.statesystem;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.linuxtools.tmf.core.interval.ITmfStateInterval;
+import org.eclipse.linuxtools.tmf.core.interval.TmfStateInterval;
+import org.eclipse.linuxtools.tmf.core.statesystem.helpers.IStateHistoryBackend;
+import org.eclipse.linuxtools.tmf.core.statevalue.ITmfStateValue;
+import org.eclipse.linuxtools.tmf.core.statevalue.TmfStateValue;
+
+/**
+ * The Transient State is used to build intervals from puntual state changse. It
+ * contains a "state info" vector similar to the "current state", except here we
+ * also record the start time of every state stored in it.
+ * 
+ * We can then build StateInterval's, to be inserted in the State History when
+ * we detect state changes : the "start time" of the interval will be the
+ * recorded time we have here, and the "end time" will be the timestamp of the
+ * new state-changing event we just read.
+ * 
+ * @author alexmont
+ * 
+ */
+class TransientState {
+
+    /* Indicates where to insert state changes that we generate */
+    private final IStateHistoryBackend backend;
+
+    private boolean isActive;
+    private long latestTime;
+
+    private ArrayList<ITmfStateValue> ongoingStateInfo;
+    private ArrayList<Long> ongoingStateStartTimes;
+
+    TransientState(IStateHistoryBackend backend) {
+        this.backend = backend;
+        isActive = true;
+        ongoingStateInfo = new ArrayList<ITmfStateValue>();
+        ongoingStateStartTimes = new ArrayList<Long>();
+
+        if (backend != null) {
+            latestTime = backend.getStartTime();
+        } else {
+            latestTime = 0;
+        }
+    }
+
+    long getLatestTime() {
+        return latestTime;
+    }
+
+    ITmfStateValue getOngoingStateValue(int index)
+            throws AttributeNotFoundException {
+
+        checkValidAttribute(index);
+        return ongoingStateInfo.get(index);
+    }
+
+    void changeOngoingStateValue(int index, ITmfStateValue newValue)
+            throws AttributeNotFoundException {
+
+        checkValidAttribute(index);
+        ongoingStateInfo.set(index, newValue);
+    }
+
+    /**
+     * Return the "ongoing" value for a given attribute as a dummy interval
+     * whose end time = -1 (since we don't know its real end time yet).
+     * 
+     * @param quark
+     * @throws AttributeNotFoundException
+     */
+    ITmfStateInterval getOngoingInterval(int quark)
+            throws AttributeNotFoundException {
+
+        checkValidAttribute(quark);
+        return new TmfStateInterval(ongoingStateStartTimes.get(quark), -1, quark,
+                ongoingStateInfo.get(quark));
+    }
+
+    private void checkValidAttribute(int quark)
+            throws AttributeNotFoundException {
+
+        if (quark > ongoingStateInfo.size() - 1 || quark < 0) {
+            throw new AttributeNotFoundException();
+        }
+    }
+
+    /**
+     * Batch method of changeOngoingStateValue(), updates the complete
+     * ongoingStateInfo in one go. BE VERY CAREFUL WITH THIS! Especially with
+     * the sizes of both arrays.
+     * 
+     * Note that the new ongoingStateInfo will be a shallow copy of
+     * newStateInfo, so that last one must be already instantiated and all.
+     * 
+     * @param newStateInfo
+     *            The List of StateValues to replace the old ongoingStateInfo
+     *            one.
+     */
+    void changeOngoingStateInfo(ArrayList<ITmfStateValue> newStateInfo) {
+        this.ongoingStateInfo = newStateInfo;
+    }
+
+    /**
+     * Add an "empty line" to both "ongoing..." vectors. This is needed so the
+     * Ongoing... tables can stay in sync with the number of attributes in the
+     * attribute tree, namely when we add sub-path attributes.
+     */
+    synchronized void addEmptyEntry() {
+        /*
+         * Since this is a new attribute, we suppose it was in the "null state"
+         * since the beginning (so we can have intervals covering for all
+         * timestamps). A null interval will then get added at the first state
+         * change.
+         */
+        ongoingStateInfo.add(TmfStateValue.nullValue());
+
+        if (backend == null) {
+            ongoingStateStartTimes.add(0L);
+        } else {
+            ongoingStateStartTimes.add(backend.getStartTime());
+        }
+    }
+
+    /**
+     * Ask if the state information about attribute 'quark' at time 'time' is
+     * present in the Builder as it is right now. If it's not, it's either in
+     * the History Tree, or not in the system at all.
+     * 
+     * Note that this method does not return the value itself (we don't even
+     * look for it, we can know by just looking at the timestamp)
+     * 
+     * @param time
+     *            The timestamp to look for
+     * @param quark
+     *            The quark of the attribute to look for
+     * @return True if the value is present in the Transient State at this
+     *         moment in time, false if it's not
+     */
+    boolean hasInfoAboutStateOf(long time, int quark) {
+        return (this.isActive() && time >= ongoingStateStartTimes.get(quark));
+    }
+
+    /**
+     * This is the lower-level method that will be called by the
+     * StateHistorySystem (with already-built StateValues and timestamps)
+     * 
+     * @param index
+     *            The index in the vectors (== the quark of the attribute)
+     * @param value
+     *            The new StateValue associated to this attribute
+     * @param eventTime
+     *            The timestamp associated with this state change
+     * @throws TimeRangeException
+     * @throws AttributeNotFoundException
+     */
+    synchronized void processStateChange(long eventTime,
+            ITmfStateValue value, int index) throws TimeRangeException,
+            AttributeNotFoundException {
+        assert (this.isActive);
+        checkValidAttribute(index);
+
+        if (latestTime < eventTime) {
+            latestTime = eventTime;
+        }
+
+        if (ongoingStateInfo.get(index).equals(value)) {
+            /*
+             * This is the case where the new value and the one already present
+             * in the Builder are the same. We do not need to create an
+             * interval, we'll just keep the current one going.
+             */
+            return;
+        }
+
+        if (backend != null && ongoingStateStartTimes.get(index) < eventTime) {
+            /*
+             * These two conditions are necessary to create an interval and
+             * update ongoingStateInfo.
+             */
+            backend.insertPastState(ongoingStateStartTimes.get(index), /*
+                                                                        * Start
+                                                                        * Time
+                                                                        */
+                    eventTime - 1, /* End Time */
+                    index, /* attribute quark */
+                    ongoingStateInfo.get(index)); /* StateValue */
+
+            ongoingStateStartTimes.set(index, eventTime);
+        }
+        ongoingStateInfo.set(index, value);
+        return;
+    }
+
+    /**
+     * Run a "get state at time" query on the Transient State only.
+     * 
+     * @param stateInfo
+     *            The stateInfo object in which we will put our relevant
+     *            information
+     * @param t
+     *            The requested timestamp
+     */
+    void doQuery(List<ITmfStateInterval> stateInfo, long t) {
+        ITmfStateInterval interval;
+
+        if (!this.isActive) {
+            return;
+        }
+        assert (stateInfo.size() == ongoingStateInfo.size());
+
+        for (int i = 0; i < ongoingStateInfo.size(); i++) {
+            /*
+             * We build a dummy interval with end time = -1 to put in the answer
+             * to the query.
+             */
+            if (this.hasInfoAboutStateOf(t, i)) {
+                interval = new TmfStateInterval(ongoingStateStartTimes.get(i), -1,
+                        i, ongoingStateInfo.get(i));
+                stateInfo.set(i, interval);
+            }
+        }
+    }
+
+    /**
+     * Close off the Transient State, used for example when we are done reading a
+     * static trace file. All the information currently contained in it will be
+     * converted to intervals and "flushed" to the State History.
+     */
+    void closeTransientState(long endTime) {
+        assert (this.isActive);
+
+        for (int i = 0; i < ongoingStateInfo.size(); i++) {
+            if (ongoingStateStartTimes.get(i) == endTime) {
+                /*
+                 * Handle this rare case where trace end == timetamp of last
+                 * state change
+                 */
+                continue;
+            }
+            try {
+                backend.insertPastState(ongoingStateStartTimes.get(i), /*
+                                                                        * Start
+                                                                        * Time
+                                                                        */
+                        endTime, /* End Time */
+                        i, /* attribute quark */
+                        ongoingStateInfo.get(i)); /* StateValue */
+
+            } catch (TimeRangeException e) {
+                /*
+                 * This shouldn't happen, since we control where the interval's
+                 * start time comes from
+                 */
+                e.printStackTrace();
+            }
+        }
+
+        ongoingStateInfo.clear();
+        ongoingStateStartTimes.clear();
+        this.isActive = false;
+        return;
+    }
+
+    /**
+     * Simply returns if this Transient State is currently being used or not
+     * 
+     * @return
+     */
+    boolean isActive() {
+        return this.isActive;
+    }
+
+    void setInactive() {
+        isActive = false;
+    }
+
+    /**
+     * Debugging method that prints the contents of both 'ongoing...' vectors
+     * 
+     * @param writer
+     */
+    void debugPrint(PrintWriter writer) {
+        /* Only used for debugging, shouldn't be externalized */
+        writer.println("------------------------------"); //$NON-NLS-1$
+        writer.println("Info stored in the Builder:"); //$NON-NLS-1$
+        if (!this.isActive) {
+            writer.println("Builder is currently inactive"); //$NON-NLS-1$
+            writer.println('\n');
+            return;
+        }
+        writer.println("\nAttribute\tStateValue\tValid since time"); //$NON-NLS-1$
+        for (int i = 0; i < ongoingStateInfo.size(); i++) {
+            writer.format("%d\t\t", i); //$NON-NLS-1$
+            writer.print(ongoingStateInfo.get(i).toString() + "\t\t"); //$NON-NLS-1$
+            writer.println(ongoingStateStartTimes.get(i).toString());
+        }
+        writer.println('\n');
+        return;
+    }
+
+}
\ No newline at end of file
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/backend/historytree/CoreNode.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/backend/historytree/CoreNode.java
new file mode 100644 (file)
index 0000000..12bde68
--- /dev/null
@@ -0,0 +1,178 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * Copyright (c) 2010, 2011 École Polytechnique de Montréal
+ * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
+ * 
+ * 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
+ * 
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.statesystem.backend.historytree;
+
+import java.nio.ByteBuffer;
+
+/**
+ * A Core node is a first-level node of a History Tree which is not a leaf node.
+ * 
+ * It extends HTNode by adding support for child nodes, and also extensions.
+ * 
+ * @author alexmont
+ * 
+ */
+class CoreNode extends HTNode {
+
+    /* Nb. of children this node has */
+    private int nbChildren;
+
+    /* Seq. numbers of the children nodes (size = MAX_NB_CHILDREN) */
+    private int[] children;
+
+    /* Start times of each of the children (size = MAX_NB_CHILDREN) */
+    private long[] childStart;
+
+    /* Seq number of this node's extension. -1 if none */
+    private int extension;
+
+    /**
+     * Initial constructor. Use this to initialize a new EMPTY node.
+     * 
+     * @param tree
+     *            The HistoryTree to which this node belongs
+     * @param seqNumber
+     *            The (unique) sequence number assigned to this particular node
+     * @param parentSeqNumber
+     *            The sequence number of this node's parent node
+     * @param start
+     *            The earliest timestamp stored in this node
+     */
+    CoreNode(HistoryTree tree, int seqNumber, int parentSeqNumber,
+            long start) {
+        super(tree, seqNumber, parentSeqNumber, start);
+        this.nbChildren = 0;
+
+        /*
+         * We instantiate the two following arrays at full size right away,
+         * since we want to reserve that space in the node's header.
+         * "this.nbChildren" will tell us how many relevant entries there are in
+         * those tables.
+         */
+        this.children = new int[ownerTree.config.maxChildren];
+        this.childStart = new long[ownerTree.config.maxChildren];
+    }
+
+    @Override
+    protected void readSpecificHeader(ByteBuffer buffer) {
+        int i;
+
+        extension = buffer.getInt();
+        nbChildren = buffer.getInt();
+
+        children = new int[ownerTree.config.maxChildren];
+        for (i = 0; i < nbChildren; i++) {
+            children[i] = buffer.getInt();
+        }
+        for (i = nbChildren; i < ownerTree.config.maxChildren; i++) {
+            buffer.getInt();
+        }
+
+        this.childStart = new long[ownerTree.config.maxChildren];
+        for (i = 0; i < nbChildren; i++) {
+            childStart[i] = buffer.getLong();
+        }
+        for (i = nbChildren; i < ownerTree.config.maxChildren; i++) {
+            buffer.getLong();
+        }
+    }
+
+    @Override
+    protected void writeSpecificHeader(ByteBuffer buffer) {
+        int i;
+
+        buffer.putInt(extension);
+        buffer.putInt(nbChildren);
+
+        /* Write the "children's seq number" array */
+        for (i = 0; i < nbChildren; i++) {
+            buffer.putInt(children[i]);
+        }
+        for (i = nbChildren; i < ownerTree.config.maxChildren; i++) {
+            buffer.putInt(0);
+        }
+
+        /* Write the "children's start times" array */
+        for (i = 0; i < nbChildren; i++) {
+            buffer.putLong(childStart[i]);
+        }
+        for (i = nbChildren; i < ownerTree.config.maxChildren; i++) {
+            buffer.putLong(0);
+        }
+    }
+
+    int getNbChildren() {
+        return nbChildren;
+    }
+
+    int getChild(int index) {
+        return children[index];
+    }
+
+    int getLatestChild() {
+        return children[nbChildren - 1];
+    }
+
+    long getChildStart(int index) {
+        return childStart[index];
+    }
+
+    long getLatestChildStart() {
+        return childStart[nbChildren - 1];
+    }
+
+    int getExtensionSequenceNumber() {
+        return extension;
+    }
+
+    /**
+     * Tell this node that it has a new child (Congrats!)
+     * 
+     * @param childNode
+     *            The SHTNode object of the new child
+     */
+    void linkNewChild(CoreNode childNode) {
+        assert (this.nbChildren < ownerTree.config.maxChildren);
+
+        this.children[nbChildren] = childNode.getSequenceNumber();
+        this.childStart[nbChildren] = childNode.getNodeStart();
+        this.nbChildren++;
+    }
+
+    @Override
+    protected byte getNodeType() {
+        return 1;
+    }
+
+    @Override
+    protected int getTotalHeaderSize() {
+        int specificSize;
+        specificSize = 4 /* 1x int (extension node) */
+                + 4 /* 1x int (nbChildren) */
+
+                /* MAX_NB * int ('children' table) */
+                + 4 * ownerTree.config.maxChildren
+
+                /* MAX_NB * Timevalue ('childStart' table) */
+                + 8 * ownerTree.config.maxChildren;
+
+        return getCommonHeaderSize() + specificSize;
+    }
+
+    @Override
+    protected String toStringSpecific() {
+        /* Only used for debugging, shouldn't be externalized */
+        return "Core Node, " + nbChildren + " children, "; //$NON-NLS-1$ //$NON-NLS-2$
+    }
+
+}
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/backend/historytree/HTConfig.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/backend/historytree/HTConfig.java
new file mode 100644 (file)
index 0000000..d86a398
--- /dev/null
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * Copyright (c) 2010, 2011 École Polytechnique de Montréal
+ * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
+ * 
+ * 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
+ * 
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.statesystem.backend.historytree;
+
+import java.io.File;
+
+/**
+ * Configuration object for a StateHistoryTree.
+ * 
+ * @author alexmont
+ * 
+ */
+final class HTConfig {
+
+    public final File stateFile;
+    public final int blockSize;
+    public final int maxChildren;
+    public final long treeStart;
+
+    HTConfig(File newStateFile, int blockSize, int maxChildren, long startTime) {
+        this.stateFile = newStateFile;
+        this.blockSize = blockSize;
+        this.maxChildren = maxChildren;
+        this.treeStart = startTime;
+    }
+
+    /**
+     * Version using default values for blocksize and maxchildren
+     * 
+     * @param stateFileName
+     * @param startTime
+     */
+    HTConfig(File newStateFile, long startTime) {
+        this(newStateFile, 64 * 1024, 50, startTime);
+    }
+}
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/backend/historytree/HTInterval.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/backend/historytree/HTInterval.java
new file mode 100644 (file)
index 0000000..0ac743e
--- /dev/null
@@ -0,0 +1,279 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * Copyright (c) 2010, 2011 École Polytechnique de Montréal
+ * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
+ * 
+ * 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
+ * 
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.statesystem.backend.historytree;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.linuxtools.tmf.core.interval.ITmfStateInterval;
+import org.eclipse.linuxtools.tmf.core.statesystem.TimeRangeException;
+import org.eclipse.linuxtools.tmf.core.statevalue.ITmfStateValue;
+import org.eclipse.linuxtools.tmf.core.statevalue.StateValueTypeException;
+import org.eclipse.linuxtools.tmf.core.statevalue.TmfStateValue;
+
+/**
+ * The interval component, which will be contained in a node of the History
+ * Tree.
+ * 
+ * @author alexmont
+ * 
+ */
+final class HTInterval implements ITmfStateInterval, Comparable<HTInterval> {
+
+    private final long start;
+    private final long end;
+    private final int attribute;
+    private final TmfStateValue sv;
+
+    /*
+     * Size of the strings section entry used by this interval (= 0 if not used)
+     */
+    private final int stringsEntrySize;
+
+    /**
+     * Standard constructor
+     * 
+     * @param intervalStart
+     * @param intervalEnd
+     * @param attribute
+     * @param value
+     * @throws TimeRangeException
+     */
+    HTInterval(long intervalStart, long intervalEnd, int attribute,
+            TmfStateValue value) throws TimeRangeException {
+        if (intervalStart > intervalEnd) {
+            throw new TimeRangeException();
+        }
+
+        this.start = intervalStart;
+        this.end = intervalEnd;
+        this.attribute = attribute;
+        this.sv = value;
+        this.stringsEntrySize = computeStringsEntrySize();
+    }
+
+    /**
+     * Reader constructor. Builds the interval using an already-allocated
+     * ByteBuffer, which normally comes from a NIO FileChannel.
+     * 
+     * @param buffer
+     *            The ByteBuffer from which to read the information
+     * @throws IOException
+     */
+    final static HTInterval readFrom(ByteBuffer buffer) throws IOException {
+        HTInterval interval;
+        long intervalStart, intervalEnd;
+        int attribute;
+        TmfStateValue value;
+        int valueOrOffset, valueSize, res;
+        byte valueType;
+        byte array[];
+
+        /* Read the Data Section entry */
+        intervalStart = buffer.getLong();
+        intervalEnd = buffer.getLong();
+        attribute = buffer.getInt();
+
+        /* Read the 'type' of the value, then react accordingly */
+        valueType = buffer.get();
+        if (valueType <= 0) {
+            /* the type of ValueOrOffset is 'value' */
+            valueOrOffset = buffer.getInt();
+            if (valueOrOffset == -1) {
+                /* Null value */
+                value = TmfStateValue.nullValue();
+            } else {
+                /* Normal integer value */
+                value = TmfStateValue.newValueInt(valueOrOffset);
+            }
+
+        } else { // valueType > 0
+            /* the type is 'offset' */
+            valueOrOffset = buffer.getInt();
+
+            /*
+             * Go read the corresponding entry in the Strings section of the
+             * block
+             */
+            buffer.mark();
+            buffer.position(valueOrOffset);
+
+            /* the first byte = the size to read */
+            valueSize = buffer.get();
+
+            /*
+             * Careful though, 'valueSize' is the total size of the entry,
+             * including the 'size' byte at the start and end (0'ed) byte at the
+             * end. Here we want 'array' to only contain the real payload of the
+             * value.
+             */
+            array = new byte[valueSize - 2];
+            buffer.get(array);
+            value = TmfStateValue.newValueString(new String(array));
+
+            /* Confirm the 0'ed byte at the end */
+            res = buffer.get();
+            if (res != 0) {
+                throw new IOException(
+                        "Invalid interval data. Maybe your file is corrupt?"); //$NON-NLS-1$
+            }
+
+            /*
+             * Restore the file pointer's position (so we can read the next
+             * interval)
+             */
+            buffer.reset();
+        }
+
+        try {
+            interval = new HTInterval(intervalStart, intervalEnd, attribute,
+                    value);
+        } catch (TimeRangeException e) {
+            throw new IOException(
+                    "Invalid interval data. Maybe your file is corrupt?"); //$NON-NLS-1$
+        }
+        return interval;
+    }
+
+    /**
+     * Antagonist of the previous constructor, write the Data entry
+     * corresponding to this interval in a ByteBuffer (mapped to a block in the
+     * history-file, hopefully)
+     * 
+     * @param buffer
+     *            The already-allocated ByteBuffer corresponding to a SHT Node
+     * @param endPosOfStringEntry
+     *            The initial (before calling this function for this interval)
+     *            position of the Strings Entry for this node. This will change
+     *            from one call to the other if we're writing String
+     *            StateValues.
+     * @return The size of the Strings Entry that was written, if any.
+     */
+    int writeInterval(ByteBuffer buffer, int endPosOfStringEntry) {
+        int sizeOfStringEntry;
+        byte[] byteArrayToWrite;
+
+        buffer.putLong(start);
+        buffer.putLong(end);
+        buffer.putInt(attribute);
+        buffer.put(sv.getType());
+
+        byteArrayToWrite = sv.toByteArray();
+
+        if (byteArrayToWrite == null) {
+            /* We write the 'valueOffset' field as a straight value */
+            if (sv.isNull()) {
+                buffer.putInt(0);
+            } else {
+                try {
+                    buffer.putInt(sv.unboxInt());
+                } catch (StateValueTypeException e) {
+                    /*
+                     * This should not happen, since the value told us it was of
+                     * type Integer (corrupted value?)
+                     */
+                    e.printStackTrace();
+                }
+            }
+            return 0; /* we didn't use a Strings section entry */
+
+        }
+        /*
+         * Size to write (+2 = +1 for size at the start, +1 for the 0 at the
+         * end)
+         */
+        sizeOfStringEntry = byteArrayToWrite.length + 2;
+
+        /* we use the valueOffset as an offset. */
+        buffer.putInt(endPosOfStringEntry - sizeOfStringEntry);
+        buffer.mark();
+        buffer.position(endPosOfStringEntry - sizeOfStringEntry);
+
+        /*
+         * write the Strings entry (1st byte = size, then the bytes, then
+         * the 0)
+         */
+        buffer.put((byte) sizeOfStringEntry);
+        buffer.put(byteArrayToWrite);
+        buffer.put((byte) 0);
+        assert (buffer.position() == endPosOfStringEntry);
+        buffer.reset();
+        return sizeOfStringEntry;
+    }
+
+    @Override
+    public long getStartTime() {
+        return start;
+    }
+
+    @Override
+    public long getEndTime() {
+        return end;
+    }
+
+    @Override
+    public int getAttribute() {
+        return attribute;
+    }
+
+    @Override
+    public ITmfStateValue getStateValue() {
+        return sv;
+    }
+
+    @Override
+    public boolean intersects(long timestamp) {
+        if (start <= timestamp) {
+            if (end >= timestamp) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    int getStringsEntrySize() {
+        return stringsEntrySize;
+    }
+
+    /**
+     * Total serialized size of this interval
+     * 
+     * @return
+     */
+    int getIntervalSize() {
+        return stringsEntrySize + HTNode.getDataEntrySize();
+    }
+
+    private int computeStringsEntrySize() {
+        if (sv.toByteArray() == null) {
+            return 0;
+        } 
+        return sv.toByteArray().length + 2;
+        /* (+1 for the first byte indicating the size, +1 for the 0'ed byte) */
+    }
+
+    /**
+     * Compare the END TIMES of different intervals. This is used to sort the
+     * intervals when we close down a node.
+     */
+    @Override
+    public int compareTo(HTInterval other) {
+        if (this.end < other.end) {
+            return -1;
+        } else if (this.end > other.end) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+}
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/backend/historytree/HTNode.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/backend/historytree/HTNode.java
new file mode 100644 (file)
index 0000000..60147db
--- /dev/null
@@ -0,0 +1,530 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * Copyright (c) 2010, 2011 École Polytechnique de Montréal
+ * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
+ * 
+ * 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
+ * 
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.statesystem.backend.historytree;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.linuxtools.tmf.core.interval.ITmfStateInterval;
+import org.eclipse.linuxtools.tmf.core.statesystem.TimeRangeException;
+import org.eclipse.linuxtools.tmf.core.statevalue.TmfStateValue;
+
+/**
+ * The base class for all the types of nodes that go in the History Tree.
+ * 
+ * @author alexmont
+ * 
+ */
+abstract class HTNode {
+
+    /* Reference to the History Tree to whom this node belongs */
+    protected final HistoryTree ownerTree;
+
+    /* Time range of this node */
+    private final long nodeStart;
+    private long nodeEnd;
+
+    /* Sequence number = position in the node section of the file */
+    private final int sequenceNumber;
+    private int parentSequenceNumber; /* = -1 if this node is the root node */
+
+    /* Where the Strings section begins (from the start of the node */
+    private int stringSectionOffset;
+
+    /* True if this node is closed (and to be committed to disk) */
+    private boolean isDone;
+
+    /* Vector containing all the intervals contained in this node */
+    private final ArrayList<HTInterval> intervals;
+
+    HTNode(HistoryTree tree, int seqNumber, int parentSeqNumber, long start) {
+        this.ownerTree = tree;
+        this.nodeStart = start;
+        this.sequenceNumber = seqNumber;
+        this.parentSequenceNumber = parentSeqNumber;
+
+        this.stringSectionOffset = ownerTree.config.blockSize;
+        this.isDone = false;
+        this.intervals = new ArrayList<HTInterval>();
+    }
+
+    /**
+     * Reader factory constructor. Build a Node object (of the right type) by
+     * reading a block in the file.
+     * 
+     * @param tree
+     *            Reference to the HT which will own this node
+     * @param fc
+     *            FileChannel to the history file, ALREADY SEEKED at the start
+     *            of the node.
+     * @throws IOException
+     */
+    final static HTNode readNode(HistoryTree tree, FileChannel fc)
+            throws IOException {
+        HTNode newNode = null;
+        int res, i;
+
+        ByteBuffer buffer = ByteBuffer.allocate(tree.config.blockSize);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+        buffer.clear();
+        res = fc.read(buffer);
+        assert (res == tree.config.blockSize);
+        // This often breaks, so might as well keep this code not too far...
+        // if ( res != tree.config.blockSize ) {
+        // tree.debugPrintFullTree(new PrintWriter(System.out, true), null,
+        // false);
+        // assert ( false );
+        // }
+        buffer.flip();
+
+        /* Read the common header part */
+        byte type = buffer.get();
+        long start = buffer.getLong();
+        long end = buffer.getLong();
+        int seqNb = buffer.getInt();
+        int parentSeqNb = buffer.getInt();
+        int intervalCount = buffer.getInt();
+        int stringSectionOffset = buffer.getInt();
+        boolean done = byteToBool(buffer.get());
+
+        /* Now the rest of the header depends on the node type */
+        switch (type) {
+        case 1:
+            /* Core nodes */
+            newNode = new CoreNode(tree, seqNb, parentSeqNb, start);
+            newNode.readSpecificHeader(buffer);
+            break;
+
+        // TODO implement other node types
+        // case 2:
+        // /* Leaf nodes */
+        //
+        // break;
+        //
+        //
+        // case 3:
+        // /* "Claudette" (extended) nodes */
+        //
+        // break;
+
+        default:
+            /* Unrecognized node type */
+            throw new IOException();
+        }
+
+        /*
+         * At this point, we should be done reading the header and 'buffer'
+         * should only have the intervals left
+         */
+        for (i = 0; i < intervalCount; i++) {
+            newNode.intervals.add(HTInterval.readFrom(buffer));
+        }
+
+        /* Assign the node's other information we have read previously */
+        newNode.nodeEnd = end;
+        newNode.stringSectionOffset = stringSectionOffset;
+        newNode.isDone = done;
+
+        return newNode;
+    }
+
+    final void writeSelf(FileChannel fc) throws IOException {
+        int res, size;
+        int curStringsEntryEndPos = ownerTree.config.blockSize;
+
+        ByteBuffer buffer = ByteBuffer.allocate(ownerTree.config.blockSize);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+        buffer.clear();
+
+        /* Write the common header part */
+        buffer.put(this.getNodeType());
+        buffer.putLong(nodeStart);
+        buffer.putLong(nodeEnd);
+        buffer.putInt(sequenceNumber);
+        buffer.putInt(parentSequenceNumber);
+        buffer.putInt(intervals.size());
+        buffer.putInt(stringSectionOffset);
+        buffer.put(boolToByte(isDone));
+
+        /* Now call the inner method to write the specific header part */
+        this.writeSpecificHeader(buffer);
+
+        /* Back to us, we write the intervals */
+        for (HTInterval interval : intervals) {
+            size = interval.writeInterval(buffer, curStringsEntryEndPos);
+            curStringsEntryEndPos -= size;
+        }
+
+        /*
+         * Write padding between the end of the Data section and the start of
+         * the Strings section (needed to fill the node in case there is no
+         * Strings section)
+         */
+        while (buffer.position() < stringSectionOffset) {
+            buffer.put((byte) 0);
+        }
+
+        /*
+         * If the offsets were right, the size of the Strings section should be
+         * == to the expected size
+         */
+        assert (curStringsEntryEndPos == stringSectionOffset);
+
+        /* Finally, write everything in the Buffer to disk */
+
+        // if we don't do this, flip() will lose what's after.
+        buffer.position(ownerTree.config.blockSize);
+
+        buffer.flip();
+        res = fc.write(buffer);
+        assert (res == ownerTree.config.blockSize);
+    }
+
+    /**
+     * Accessors
+     */
+    long getNodeStart() {
+        return nodeStart;
+    }
+
+    long getNodeEnd() {
+        if (this.isDone) {
+            return nodeEnd;
+        }
+        return 0;
+    }
+
+    int getSequenceNumber() {
+        return sequenceNumber;
+    }
+
+    int getParentSequenceNumber() {
+        return parentSequenceNumber;
+    }
+
+    /**
+     * Change this node's parent. Used when we create a new root node for
+     * example.
+     */
+    void setParentSequenceNumber(int newParent) {
+        parentSequenceNumber = newParent;
+    }
+
+    boolean isDone() {
+        return isDone;
+    }
+
+    /**
+     * Add an interval to this node
+     * 
+     * @param newInterval
+     */
+    void addInterval(HTInterval newInterval) {
+        /* Just in case, but should be checked before even calling this function */
+        assert (newInterval.getIntervalSize() <= this.getNodeFreeSpace());
+
+        intervals.add(newInterval);
+
+        /* Update the in-node offset "pointer" */
+        stringSectionOffset -= (newInterval.getStringsEntrySize());
+    }
+
+    /**
+     * We've received word from the containerTree that newest nodes now exist to
+     * our right. (Puts isDone = true and sets the endtime)
+     * 
+     * @param endtime
+     *            The nodeEnd time that the node will have
+     * @throws TimeRangeException
+     */
+    void closeThisNode(long endtime) {
+        assert (endtime >= this.nodeStart);
+        // /* This also breaks often too */
+        // if ( endtime.getValue() <= this.nodeStart.getValue() ) {
+        // ownerTree.debugPrintFullTree(new PrintWriter(System.out, true), null,
+        // false);
+        // assert ( false );
+        // }
+
+        if (intervals.size() > 0) {
+            /*
+             * Sort the intervals by ascending order of their end time. This
+             * speeds up lookups a bit
+             */
+            Collections.sort(intervals);
+
+            /*
+             * Make sure there are no intervals in this node with their EndTime
+             * > the one requested. Only need to check the last one since they
+             * are now sorted
+             */
+            assert (endtime >= intervals.get(intervals.size() - 1).getEndTime());
+        }
+
+        this.isDone = true;
+        this.nodeEnd = endtime;
+        return;
+    }
+
+    /**
+     * The method to fill up the stateInfo (passed on from the Current State
+     * Tree when it does a query on the SHT). We'll replace the data in that
+     * vector with whatever relevant we can find from this node
+     * 
+     * @param stateInfo
+     *            The same stateInfo that comes from SHT's doQuery()
+     * @param t
+     *            The timestamp for which the query is for. Only return
+     *            intervals that intersect t.
+     * @throws TimeRangeException
+     */
+    void writeInfoFromNode(List<ITmfStateInterval> stateInfo, long t)
+            throws TimeRangeException {
+        assert (this.isDone); // not sure this will always be the case...
+        int startIndex;
+
+        if (intervals.size() == 0) {
+            return;
+        }
+        startIndex = getStartIndexFor(t);
+
+        for (int i = startIndex; i < intervals.size(); i++) {
+            /*
+             * Now we only have to compare the Start times, since we now the End
+             * times necessarily fit
+             */
+            if (intervals.get(i).getStartTime() <= t) {
+                stateInfo.set(intervals.get(i).getAttribute(), intervals.get(i));
+            }
+        }
+        return;
+    }
+
+    /**
+     * Get a single Interval from the information in this node If the
+     * key/timestamp pair cannot be found, we return null.
+     * 
+     * @param key
+     * @param t
+     * @return The Interval containing the information we want, or null if it
+     *         wasn't found
+     * @throws TimeRangeException
+     */
+    HTInterval getRelevantInterval(int key, long t) throws TimeRangeException {
+        assert (this.isDone);
+        int startIndex;
+
+        if (intervals.size() == 0) {
+            return null;
+        }
+
+        startIndex = getStartIndexFor(t);
+
+        for (int i = startIndex; i < intervals.size(); i++) {
+            if (intervals.get(i).getAttribute() == key) {
+                if (intervals.get(i).getStartTime() <= t) {
+                    return intervals.get(i);
+                }
+            }
+        }
+        /* We didn't find the relevant information in this node */
+        return null;
+    }
+
+    private int getStartIndexFor(long t) throws TimeRangeException {
+        HTInterval dummy;
+        int index;
+
+        /*
+         * Since the intervals are sorted by end time, we can skip all the ones
+         * at the beginning whose end times are smaller than 't'. Java does
+         * provides a .binarySearch method, but its API is quite weird...
+         */
+        dummy = new HTInterval(0, t, 0, TmfStateValue.nullValue());
+        index = Collections.binarySearch(intervals, dummy);
+
+        if (index < 0) {
+            /*
+             * .binarySearch returns a negative number if the exact value was
+             * not found. Here we just want to know where to start searching, we
+             * don't care if the value is exact or not.
+             */
+            index = -index - 1;
+
+        }
+
+        /* Sometimes binarySearch yields weird stuff... */
+        if (index < 0) {
+            index = 0;
+        }
+        if (index >= intervals.size()) {
+            index = intervals.size() - 1;
+        }
+
+        /*
+         * Another API quirkiness, the returned index is the one of the *last*
+         * element of a series of equal endtimes, which happens sometimes. We
+         * want the *first* element of such a series, to read through them
+         * again.
+         */
+        while (index > 0
+                && intervals.get(index - 1).compareTo(intervals.get(index)) == 0) {
+            index--;
+        }
+        // FIXME F*ck all this, just do our own binary search in a saner way...
+
+        // //checks to make sure startIndex works how I think it does
+        // if ( startIndex > 0 ) { assert ( intervals.get(startIndex-1).getEnd()
+        // < t ); }
+        // assert ( intervals.get(startIndex).getEnd() >= t );
+        // if ( startIndex < intervals.size()-1 ) { assert (
+        // intervals.get(startIndex+1).getEnd() >= t ); }
+
+        return index;
+    }
+
+    /**
+     * @return The offset, within the node, where the Data section ends
+     */
+    private int getDataSectionEndOffset() {
+        return this.getTotalHeaderSize() + HTNode.getDataEntrySize()
+                * intervals.size();
+    }
+
+    /**
+     * Returns the free space in the node, which is simply put, the
+     * stringSectionOffset - dataSectionOffset
+     */
+    int getNodeFreeSpace() {
+        return stringSectionOffset - this.getDataSectionEndOffset();
+    }
+
+    /**
+     * Returns the current space utilisation of this node, as a percentage.
+     * (used space / total usable space, which excludes the header)
+     */
+    long getNodeUsagePRC() {
+        float freePercent = (float) this.getNodeFreeSpace()
+                / (float) (ownerTree.config.blockSize - this.getTotalHeaderSize())
+                * 100f;
+        return (long) (100L - freePercent);
+    }
+
+    protected final static int getDataEntrySize() {
+        return 16 /* 2 x Timevalue/long (interval start + end) */
+        + 4 /* int (key) */
+        + 1 /* byte (type) */
+        + 4; /* int (valueOffset) */
+        /* = 25 */
+    }
+
+    protected final static byte boolToByte(boolean thebool) {
+        if (thebool) {
+            return (byte) 1;
+        }
+        return (byte) 0;
+    }
+
+    final static boolean byteToBool(byte thebyte) {
+        return (thebyte == (byte) 1);
+    }
+
+    /**
+     * @name Debugging functions
+     */
+
+    @SuppressWarnings("nls")
+    @Override
+    public String toString() {
+        /* Only used for debugging, shouldn't be externalized */
+        StringBuffer buf = new StringBuffer("Node #" + sequenceNumber + ", ");
+        buf.append(this.toStringSpecific());
+        buf.append(intervals.size() + " intervals (" + this.getNodeUsagePRC()
+                + "% used), ");
+
+        buf.append("[" + this.nodeStart + " - ");
+        if (this.isDone) {
+            buf = buf.append("" + this.nodeEnd + "]");
+        } else {
+            buf = buf.append("...]");
+        }
+        return buf.toString();
+    }
+
+    /**
+     * Debugging function that prints out the contents of this node
+     * 
+     * @param writer
+     *            PrintWriter in which we will print the debug output
+     */
+    @SuppressWarnings("nls")
+    void debugPrintIntervals(PrintWriter writer) {
+        /* Only used for debugging, shouldn't be externalized */
+        writer.println("Node #" + sequenceNumber + ":");
+
+        /* Array of children */
+        if (this.getNodeType() == 1) { /* Only Core Nodes can have children */
+            CoreNode thisNode = (CoreNode) this;
+            writer.print("  " + thisNode.getNbChildren() + " children");
+            if (thisNode.getNbChildren() >= 1) {
+                writer.print(": [ " + thisNode.getChild(0));
+                for (int i = 1; i < thisNode.getNbChildren(); i++) {
+                    writer.print(", " + thisNode.getChild(i));
+                }
+                writer.print(']');
+            }
+            writer.print('\n');
+        }
+
+        /* List of intervals in the node */
+        writer.println("  Intervals contained:");
+        for (int i = 0; i < intervals.size(); i++) {
+            writer.println(intervals.get(i).toString());
+        }
+        writer.println('\n');
+    }
+
+    final static int getCommonHeaderSize() {
+        /*
+         * 1 - byte (type)
+         * 
+         * 16 - 2x long (start time, end time)
+         * 
+         * 16 - 4x int (seq number, parent seq number, intervalcount, strings
+         * section pos.)
+         * 
+         * 1 - byte (done or not)
+         */
+        return 34;
+    }
+
+    /**
+     * @name Abstract methods
+     */
+
+    protected abstract byte getNodeType();
+
+    protected abstract int getTotalHeaderSize();
+
+    protected abstract void readSpecificHeader(ByteBuffer buffer);
+
+    protected abstract void writeSpecificHeader(ByteBuffer buffer);
+
+    protected abstract String toStringSpecific();
+}
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/backend/historytree/HT_IO.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/backend/historytree/HT_IO.java
new file mode 100644 (file)
index 0000000..66bdcb6
--- /dev/null
@@ -0,0 +1,168 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * Copyright (c) 2010, 2011 École Polytechnique de Montréal
+ * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
+ * 
+ * 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
+ * 
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.statesystem.backend.historytree;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+
+/**
+ * This class exists mainly for code isolation/clarification purposes. It
+ * contains all the methods and descriptors to handle reading/writing to the
+ * tree-file on disk and all the caching mechanisms. Every HistoryTree should
+ * contain 1 and only 1 HT_IO element.
+ * 
+ * @author alexmont
+ * 
+ */
+class HT_IO {
+
+    /* reference to the tree to which this IO-object belongs */
+    private final HistoryTree tree;
+
+    /* Fields related to the file I/O */
+    private FileInputStream fis;
+    private FileOutputStream fos;
+    private FileChannel fcIn;
+    private FileChannel fcOut;
+
+    /**
+     * Standard constructor
+     * 
+     * @param tree
+     * @param newFile Are we creating a new file from scratch?
+     * @throws IOException
+     */
+    HT_IO(HistoryTree tree, boolean newFile) throws IOException {
+        this.tree = tree;
+        File historyTreeFile = tree.config.stateFile;
+
+        if (newFile) {
+            /* Create a new empty History Tree file */
+            if (historyTreeFile.exists()) {
+                historyTreeFile.delete();
+            }
+            historyTreeFile.createNewFile();
+            fis = new FileInputStream(historyTreeFile);
+            fos = new FileOutputStream(historyTreeFile, false);
+        } else {
+            /*
+             * We want to open an existing file, make sure we don't squash the
+             * existing content when opening the fos!
+             */
+            this.fis = new FileInputStream(historyTreeFile);
+            this.fos = new FileOutputStream(historyTreeFile, true);
+        }
+        this.fcIn = fis.getChannel();
+        this.fcOut = fos.getChannel();
+    }
+
+    /**
+     * Generic "read node" method, which checks if the node is in memory first,
+     * and if it's not it goes to disk to retrieve it.
+     * 
+     * @param seqNumber
+     *            Sequence number of the node we want
+     * @return The wanted node in object form
+     */
+    HTNode readNode(int seqNumber) {
+        HTNode node = readNodeFromMemory(seqNumber);
+        if (node == null) {
+            return readNodeFromDisk(seqNumber);
+        }
+        return node;
+    }
+
+    private HTNode readNodeFromMemory(int seqNumber) {
+        for (HTNode node : tree.latestBranch) {
+            if (node.getSequenceNumber() == seqNumber) {
+                return node;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * This method here isn't private, if we know for sure the node cannot be in
+     * memory it's a bit faster to use this directly (when opening a file from
+     * disk for example)
+     */
+    HTNode readNodeFromDisk(int seqNumber) {
+        HTNode readNode;
+        try {
+            seekFCToNodePos(fcIn, seqNumber);
+            readNode = HTNode.readNode(tree, fcIn);
+            return readNode;
+        } catch (IOException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    void writeNode(HTNode node) {
+        try {
+            /* Position ourselves at the start of the node and write it */
+            seekFCToNodePos(fcOut, node.getSequenceNumber());
+            node.writeSelf(fcOut);
+        } catch (IOException e) {
+            /* If we were able to open the file, we should be fine now... */
+            e.printStackTrace();
+        }
+    }
+
+    FileChannel getFcOut() {
+        return this.fcOut;
+    }
+
+    FileInputStream supplyATReader() {
+        try {
+            /*
+             * Position ourselves at the start of the Mapping section in the
+             * file (which is right after the Blocks)
+             */
+            seekFCToNodePos(fcIn, tree.getNodeCount());
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return fis;
+    }
+
+    File supplyATWriterFile() {
+        return tree.config.stateFile;
+    }
+
+    long supplyATWriterFilePos() {
+        return HistoryTree.getTreeHeaderSize()
+                + ((long) tree.getNodeCount() * tree.config.blockSize);
+    }
+
+    /**
+     * Seek the given FileChannel to the position corresponding to the node that
+     * has seqNumber
+     * 
+     * @param seqNumber
+     * @throws IOException
+     */
+    private void seekFCToNodePos(FileChannel fc, int seqNumber)
+            throws IOException {
+        fc.position(HistoryTree.getTreeHeaderSize() + (long) seqNumber
+                * tree.config.blockSize);
+        /*
+         * cast to (long) is needed to make sure the result is a long too and
+         * doesn't get truncated
+         */
+    }
+
+}
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/backend/historytree/HistoryTree.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/backend/historytree/HistoryTree.java
new file mode 100644 (file)
index 0000000..78cb75d
--- /dev/null
@@ -0,0 +1,633 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * Copyright (c) 2010, 2011 École Polytechnique de Montréal
+ * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
+ * 
+ * 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
+ * 
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.statesystem.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.FileChannel;
+import java.util.Vector;
+
+import org.eclipse.linuxtools.tmf.core.statesystem.TimeRangeException;
+
+/**
+ * Meta-container for the History Tree. This structure contains all the
+ * high-level data relevant to the tree.
+ * 
+ * @author alexmont
+ * 
+ */
+class HistoryTree {
+
+    private static final int HISTORY_FILE_MAGIC_NUMBER = 0x05FFA900;
+
+    /**
+     * File format version. Increment minor on backwards-compatible changes.
+     * Increment major + set minor back to 0 when breaking compatibility.
+     */
+    private static final int MAJOR_VERSION = 3;
+    private static final byte MINOR_VERSION = 0;
+
+    /**
+     * Tree-specific configuration
+     */
+    /* Container for all the configuration constants */
+    protected final HTConfig config;
+
+    /* Reader/writer object */
+    private final HT_IO treeIO;
+
+    /**
+     * Variable Fields (will change throughout the existance of the SHT)
+     */
+    /* Latest timestamp found in the tree (at any given moment) */
+    private long treeEnd;
+
+    /* How many nodes exist in this tree, total */
+    private int nodeCount;
+
+    /* "Cache" to keep the active nodes in memory */
+    protected Vector<CoreNode> latestBranch;
+
+    /**
+     * Create a new State History from scratch, using a SHTConfig object for
+     * configuration
+     * 
+     * @param conf
+     * @throws IOException
+     */
+    private HistoryTree(HTConfig conf) throws IOException {
+        /*
+         * Simple assertion to make sure we have enough place in the 0th block
+         * for the tree configuration
+         */
+        assert (conf.blockSize >= getTreeHeaderSize());
+
+        config = conf;
+        treeEnd = conf.treeStart;
+        nodeCount = 0;
+        latestBranch = new Vector<CoreNode>();
+
+        /* Prepare the IO object */
+        treeIO = new HT_IO(this, true);
+
+        /* Add the first node to the tree */
+        CoreNode firstNode = initNewCoreNode(-1, conf.treeStart);
+        latestBranch.add(firstNode);
+    }
+
+    /**
+     * "New State History" constructor, which doesn't use SHTConfig but the
+     * individual values separately. Kept for now for backwards compatibility,
+     * but you should definitely consider using SHTConfig instead (since its
+     * contents can then change without directly affecting SHT's API).
+     */
+    HistoryTree(File newStateFile, int blockSize, int maxChildren,
+            long startTime) throws IOException {
+        this(new HTConfig(newStateFile, blockSize, maxChildren, startTime));
+    }
+
+    /**
+     * "Reader" constructor : instantiate a SHTree from an existing tree file on
+     * disk
+     * 
+     * @param existingFileName
+     *            Path/filename of the history-file we are to open
+     * @throws IOException
+     */
+    HistoryTree(File existingStateFile) 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 ts;
+
+        /* Java I/O mumbo jumbo... */
+        if ((!existingStateFile.exists()) || existingStateFile.length() <= 0) {
+            throw new IOException("Invalid state file selected"); //$NON-NLS-1$
+        }
+
+        FileInputStream fis = new FileInputStream(existingStateFile);
+        ByteBuffer buffer = ByteBuffer.allocate(getTreeHeaderSize());
+        FileChannel fc = fis.getChannel();
+        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("Selected file does not look like a History Tree file"); //$NON-NLS-1$
+        }
+
+        res = buffer.getInt(); /* Major version number */
+        if (res != MAJOR_VERSION) {
+            throw new IOException("Select History Tree file is of an older " //$NON-NLS-1$
+                    + "format. Please use a previous version of " //$NON-NLS-1$
+                    + "the parser to open it."); //$NON-NLS-1$
+        }
+
+        res = buffer.getInt(); /* Minor version number */
+
+        bs = buffer.getInt(); /* Block Size */
+        maxc = buffer.getInt(); /* Max nb of children per node */
+
+        this.nodeCount = buffer.getInt();
+        rootNodeSeqNb = buffer.getInt();
+
+        /* Go read the start time, which is also the root node's start time */
+        // TODO maybe make this part less ugly, as we need treeStart to build
+        // the SHTConfig object, but we need that object for new SHT_IO()
+        // and rebuildLatestBranch() ...
+        fc.position(getTreeHeaderSize() + (long) rootNodeSeqNb * bs);
+        buffer = ByteBuffer.allocate(8);
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+        buffer.clear();
+        res = fc.read(buffer);
+        assert (res == 8);
+        buffer.flip();
+        ts = buffer.getLong();
+
+        this.config = new HTConfig(existingStateFile, bs, maxc, ts);
+        fis.close();
+        /*
+         * 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(this, false);
+
+        rebuildLatestBranch(rootNodeSeqNb);
+        ts = latestBranch.firstElement().getNodeStart();
+        this.treeEnd = latestBranch.firstElement().getNodeEnd();
+    }
+
+    /**
+     * "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
+     */
+    void closeTree() {
+        FileChannel fc;
+        ByteBuffer buffer;
+        int i, res;
+
+        /* Close off the latest branch of the tree */
+        for (i = 0; i < latestBranch.size(); i++) {
+            latestBranch.get(i).closeThisNode(treeEnd);
+            treeIO.writeNode(latestBranch.get(i));
+        }
+
+        /* Only use this for debugging purposes, it's VERY slow! */
+        // this.checkIntegrity();
+
+        fc = treeIO.getFcOut();
+        buffer = ByteBuffer.allocate(getTreeHeaderSize());
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+        buffer.clear();
+
+        /* Save the config of the tree to the header of the file */
+        try {
+            fc.position(0);
+
+            buffer.putInt(HISTORY_FILE_MAGIC_NUMBER);
+
+            buffer.putInt(MAJOR_VERSION);
+            buffer.putInt(MINOR_VERSION);
+
+            buffer.putInt(config.blockSize);
+            buffer.putInt(config.maxChildren);
+
+            buffer.putInt(nodeCount);
+
+            /* root node seq. nb */
+            buffer.putInt(latestBranch.firstElement().getSequenceNumber());
+
+            buffer.flip();
+            res = fc.write(buffer);
+            assert (res <= getTreeHeaderSize());
+            /* done writing the file header */
+
+        } catch (IOException e) {
+            /* We should have any problems at this point... */
+            e.printStackTrace();
+        }
+        return;
+    }
+
+    /**
+     * @name Accessors
+     */
+    
+    long getTreeStart() {
+        return config.treeStart;
+    }
+
+    long getTreeEnd() {
+        return treeEnd;
+    }
+
+    int getNodeCount() {
+        return nodeCount;
+    }
+
+    HT_IO getTreeIO() {
+        return treeIO;
+    }
+
+    /**
+     * 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
+     */
+    private void rebuildLatestBranch(int rootNodeSeqNb) {
+        HTNode nextChildNode;
+
+        this.latestBranch = new Vector<CoreNode>();
+
+        nextChildNode = treeIO.readNodeFromDisk(rootNodeSeqNb);
+        latestBranch.add((CoreNode) nextChildNode);
+        while (latestBranch.lastElement().getNbChildren() > 0) {
+            nextChildNode = treeIO.readNodeFromDisk(latestBranch.lastElement().getLatestChild());
+            latestBranch.add((CoreNode) nextChildNode);
+        }
+    }
+
+    /**
+     * Insert an interval in the tree
+     * 
+     * @param interval
+     */
+    void insertInterval(HTInterval interval) throws TimeRangeException {
+        if (interval.getStartTime() < config.treeStart) {
+            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();
+        }
+        return;
+    }
+
+    /**
+     * 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) {
+        int i;
+        CoreNode newNode, prevNode;
+        long splitTime = treeEnd;
+
+        assert (indexOfNode < latestBranch.size());
+
+        /* 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 (latestBranch.get(indexOfNode - 1).getNbChildren() == config.maxChildren) {
+            /* If not, add a branch starting one level higher instead */
+            addSiblingNode(indexOfNode - 1);
+            return;
+        }
+
+        /* Split off the new branch from the old one */
+        for (i = indexOfNode; i < latestBranch.size(); i++) {
+            latestBranch.get(i).closeThisNode(splitTime);
+            treeIO.writeNode(latestBranch.get(i));
+
+            prevNode = latestBranch.get(i - 1);
+            newNode = initNewCoreNode(prevNode.getSequenceNumber(),
+                    splitTime + 1);
+            prevNode.linkNewChild(newNode);
+
+            latestBranch.set(i, newNode);
+        }
+        return;
+    }
+
+    /**
+     * Similar to the previous method, except here we rebuild a completely new
+     * latestBranch
+     */
+    private void addNewRootNode() {
+        int i, depth;
+        CoreNode oldRootNode, newRootNode, newNode, prevNode;
+        long splitTime = this.treeEnd;
+
+        oldRootNode = latestBranch.firstElement();
+        newRootNode = initNewCoreNode(-1, config.treeStart);
+
+        /* Tell the old root node that it isn't root anymore */
+        oldRootNode.setParentSequenceNumber(newRootNode.getSequenceNumber());
+
+        /* Close off the whole current latestBranch */
+        for (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 */
+        depth = latestBranch.size();
+        latestBranch = new Vector<CoreNode>();
+        latestBranch.add(newRootNode);
+        for (i = 1; i < depth + 1; i++) {
+            prevNode = latestBranch.get(i - 1);
+            newNode = initNewCoreNode(prevNode.getParentSequenceNumber(),
+                    splitTime + 1);
+            prevNode.linkNewChild(newNode);
+            latestBranch.add(newNode);
+        }
+    }
+
+    /**
+     * Add a new empty 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(this, 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
+     * @param t
+     * @return The child node intersecting t
+     */
+    HTNode selectNextChild(CoreNode currentNode, long t) {
+        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.isDone()) {
+            return treeIO.readNodeFromDisk(potentialNextSeqNb);
+        }
+        return treeIO.readNode(potentialNextSeqNb);
+    }
+
+    /**
+     * Helper function to get the 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.
+     */
+    static int getTreeHeaderSize() {
+        return 4096;
+    }
+
+    long getFileSize() {
+        return config.stateFile.length();
+    }
+
+    /**
+     * @name Test/debugging functions
+     */
+
+    /* Only used for debugging, shouldn't be externalized */
+    @SuppressWarnings("nls")
+    boolean checkNodeIntegrity(HTNode zenode) {
+        
+        HTNode otherNode;
+        CoreNode node;
+        String message = ""; //$NON-NLS-1$
+        boolean ret = true;
+
+        // FIXME /* Only testing Core Nodes for now */
+        if (!(zenode instanceof CoreNode)) {
+            return true;
+        }
+
+        node = (CoreNode) zenode;
+
+        /*
+         * 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()) {
+                message += "Start time of node (" + node.getNodeStart() + ") "
+                        + "does not match start time of first child " + "("
+                        + otherNode.getNodeStart() + "), " + "node #"
+                        + otherNode.getSequenceNumber() + ")\n";
+                ret = false;
+            }
+            if (node.isDone()) {
+                otherNode = treeIO.readNode(node.getLatestChild());
+                if (node.getNodeEnd() != otherNode.getNodeEnd()) {
+                    message += "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)) {
+                message += "  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;
+            }
+        }
+
+        if (!ret) {
+            System.out.println("");
+            System.out.println("SHT: Integrity check failed for node #"
+                    + node.getSequenceNumber() + ":");
+            System.out.println(message);
+        }
+        return ret;
+    }
+
+    void checkIntegrity() {
+        for (int i = 0; i < nodeCount; i++) {
+            checkNodeIntegrity(treeIO.readNode(i));
+        }
+    }
+
+    /* Only used for debugging, shouldn't be externalized */
+    @SuppressWarnings("nls")
+    @Override
+    public String toString() {
+        return "Information on the current tree:\n\n" + "Blocksize: "
+                + config.blockSize + "\n" + "Max nb. of children per node: "
+                + config.maxChildren + "\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.firstElement().getSequenceNumber() + "\n"
+                + "'Latest leaf' has sequence number: "
+                + latestBranch.lastElement().getSequenceNumber();
+    }
+
+    private int curDepth;
+
+    /**
+     * 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.
+     */
+    @SuppressWarnings("nls")
+    private void preOrderPrint(PrintWriter writer, boolean printIntervals,
+            CoreNode currentNode) {
+        /* Only used for debugging, shouldn't be externalized */
+        int i, j;
+        HTNode nextNode;
+
+        writer.println(currentNode.toString());
+        if (printIntervals) {
+            currentNode.debugPrintIntervals(writer);
+        }
+        curDepth++;
+
+        for (i = 0; i < currentNode.getNbChildren(); i++) {
+            nextNode = treeIO.readNode(currentNode.getChild(i));
+            assert (nextNode instanceof CoreNode); // TODO temporary
+            for (j = 0; j < curDepth - 1; j++) {
+                writer.print("  ");
+            }
+            writer.print("+-");
+            preOrderPrint(writer, printIntervals, (CoreNode) nextNode);
+        }
+        curDepth--;
+        return;
+    }
+
+    /**
+     * Print out the full tree for debugging purposes
+     * 
+     * @param writer
+     *            PrintWriter in which to write the output
+     * @param printIntervals
+     *            Says if you want to output the full interval information
+     */
+    void debugPrintFullTree(PrintWriter writer, boolean printIntervals) {
+        /* Only used for debugging, shouldn't be externalized */
+        curDepth = 0;
+        this.preOrderPrint(writer, false, latestBranch.firstElement());
+
+        if (printIntervals) {
+            writer.println("\nDetails of intervals:"); //$NON-NLS-1$
+            curDepth = 0;
+            this.preOrderPrint(writer, true, latestBranch.firstElement());
+        }
+        writer.println('\n');
+    }
+
+}
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/backend/historytree/HistoryTreeBackend.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/backend/historytree/HistoryTreeBackend.java
new file mode 100644 (file)
index 0000000..dab8018
--- /dev/null
@@ -0,0 +1,271 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * Copyright (c) 2010, 2011 École Polytechnique de Montréal
+ * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
+ * 
+ * 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
+ * 
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.statesystem.backend.historytree;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.List;
+
+import org.eclipse.linuxtools.tmf.core.interval.ITmfStateInterval;
+import org.eclipse.linuxtools.tmf.core.statesystem.TimeRangeException;
+import org.eclipse.linuxtools.tmf.core.statesystem.helpers.IStateHistoryBackend;
+import org.eclipse.linuxtools.tmf.core.statevalue.ITmfStateValue;
+import org.eclipse.linuxtools.tmf.core.statevalue.TmfStateValue;
+
+/**
+ * History Tree backend for storing a state history. This is the basic version
+ * that runs in the same thread as the class creating it.
+ * 
+ * @author alexmont
+ * 
+ */
+public class HistoryTreeBackend implements IStateHistoryBackend {
+
+    protected final HistoryTree sht;
+    private final HT_IO treeIO;
+
+    /**
+     * Construtor for new history files. Use this when creating a new history
+     * from scratch.
+     * 
+     * @param newStateFile
+     *            The filename/location where to store the state history (Should
+     *            end in .ht)
+     * @param blockSize
+     *            The size of the blocks in the history file. This should be a
+     *            multiple of 4096.
+     * @param maxChildren
+     *            The maximum number of children each core node can have
+     * @param startTime
+     *            The earliest time stamp that will be stored in the history
+     * @throws IOException
+     *             Thrown if we can't create the file for some reason
+     */
+    public HistoryTreeBackend(File newStateFile, int blockSize,
+            int maxChildren, long startTime) throws IOException {
+        sht = new HistoryTree(newStateFile, blockSize, maxChildren, startTime);
+        treeIO = sht.getTreeIO();
+    }
+
+    /**
+     * Construtor for new history files. Use this when creating a new history
+     * from scratch. This version supplies sane defaults for the configuration
+     * parameters.
+     * 
+     * @param newStateFile
+     *            The filename/location where to store the state history (Should
+     *            end in .ht)
+     * @param startTime
+     *            The earliest time stamp that will be stored in the history
+     * @throws IOException
+     *             Thrown if we can't create the file for some reason
+     */
+    public HistoryTreeBackend(File newStateFile, long startTime)
+            throws IOException {
+        this(newStateFile, 64 * 1024, 50, startTime);
+    }
+
+    /**
+     * Existing history constructor. Use this to open an existing state-file.
+     * 
+     * @param existingFileName Filename/location of the history we want to load
+     * @throws IOException If we can't read the file, if it doesn't exist or is not recognized
+     */
+    public HistoryTreeBackend(File existingStateFile) throws IOException {
+        sht = new HistoryTree(existingStateFile);
+        treeIO = sht.getTreeIO();
+    }
+
+    @Override
+    public long getStartTime() {
+        return sht.getTreeStart();
+    }
+
+    @Override
+    public long getEndTime() {
+        return sht.getTreeEnd();
+    }
+
+    @Override
+    public void insertPastState(long stateStartTime, long stateEndTime,
+            int quark, ITmfStateValue value) throws TimeRangeException {
+        HTInterval interval = new HTInterval(stateStartTime, stateEndTime,
+                quark, (TmfStateValue) value);
+
+        /* Start insertions at the "latest leaf" */
+        sht.insertInterval(interval);
+    }
+
+    @Override
+    public void finishedBuilding(long endTime) throws TimeRangeException {
+        sht.closeTree();
+    }
+
+    @Override
+    public FileInputStream supplyAttributeTreeReader() {
+        return treeIO.supplyATReader();
+    }
+
+    @Override
+    public File supplyAttributeTreeWriterFile() {
+        return treeIO.supplyATWriterFile();
+    }
+
+    @Override
+    public long supplyAttributeTreeWriterFilePosition() {
+        return treeIO.supplyATWriterFilePos();
+    }
+
+    @Override
+    public void doQuery(List<ITmfStateInterval> stateInfo, long t)
+            throws TimeRangeException {
+        if (!checkValidTime(t)) {
+            /* We can't possibly have information about this query */
+            throw new TimeRangeException();
+        }
+
+        /* We start by reading the information in the root node */
+        // FIXME using CoreNode for now, we'll have to redo this part to handle
+        // different node types
+        CoreNode currentNode = sht.latestBranch.firstElement();
+        currentNode.writeInfoFromNode(stateInfo, t);
+
+        /* Then we follow the branch down in the relevant children */
+        while (currentNode.getNbChildren() > 0) {
+            currentNode = (CoreNode) sht.selectNextChild(currentNode, t);
+            currentNode.writeInfoFromNode(stateInfo, t);
+        }
+
+        /*
+         * The stateInfo should now be filled with everything needed, we pass
+         * the control back to the State System.
+         */
+        return;
+    }
+
+    @Override
+    public ITmfStateInterval doSingularQuery(long t, int attributeQuark)
+            throws TimeRangeException {
+        return getRelevantInterval(t, attributeQuark);
+    }
+
+    /**
+     * Simple check to make sure the requested timestamps are within the borders
+     * of this tree.
+     * 
+     * @param t
+     *            The queried timestamp
+     * @return True if it's within range, false if not.
+     */
+    private boolean checkValidTime(long t) {
+        return (t >= sht.getTreeStart() && t <= sht.getTreeEnd());
+    }
+
+    /**
+     * Inner method to find the interval in the tree containing the requested
+     * key/timestamp pair, wherever in which node it is.
+     * 
+     * @param t
+     * @param key
+     * @return The node containing the information we want
+     */
+    private HTInterval getRelevantInterval(long t, int key)
+            throws TimeRangeException {
+        if (!checkValidTime(t)) {
+            throw new TimeRangeException();
+        }
+
+        // FIXME using CoreNode for now, we'll have to redo this part to handle
+        // different node types
+        CoreNode currentNode = sht.latestBranch.firstElement();
+        HTInterval interval = currentNode.getRelevantInterval(key, t);
+
+        while (interval == null && currentNode.getNbChildren() > 0) {
+            currentNode = (CoreNode) sht.selectNextChild(currentNode, t);
+            interval = currentNode.getRelevantInterval(key, t);
+        }
+        /*
+         * Since we should now have intervals at every attribute/timestamp
+         * combination, it should NOT be null here.
+         */
+        assert (interval != null);
+        return interval;
+    }
+
+    /**
+     * Return the size of the tree history file
+     * 
+     * @return The current size of the history file in bytes
+     */
+    public long getFileSize() {
+        return sht.getFileSize();
+    }
+
+    /**
+     * Return the current depth of the tree, ie the number of node levels.
+     * 
+     * @return The tree depth
+     */
+    public int getTreeDepth() {
+        return sht.latestBranch.size();
+    }
+
+    /**
+     * Return the average node usage as a percentage (between 0 and 100)
+     * 
+     * @return Average node usage %
+     */
+    public int getAverageNodeUsage() {
+        HTNode node;
+        long total = 0;
+        long ret;
+
+        for (int seq = 0; seq < sht.getNodeCount(); seq++) {
+            node = treeIO.readNode(seq);
+            total += node.getNodeUsagePRC();
+        }
+
+        ret = total / sht.getNodeCount();
+        assert (ret >= 0 && ret <= 100);
+        return (int) ret;
+    }
+
+    @Override
+    public void debugPrint(PrintWriter writer) {
+        /* By default don't print out all the intervals */
+        this.debugPrint(writer, false);
+    }
+
+    /**
+     * The basic debugPrint method will print the tree structure, but not
+     * their contents.
+     * 
+     * This method here print the contents (the intervals) as well.
+     * 
+     * @param writer
+     * @param printIntervals
+     */
+    public void debugPrint(PrintWriter writer, boolean printIntervals) {
+        /* Only used for debugging, shouldn't be externalized */
+        writer.println("------------------------------"); //$NON-NLS-1$
+        writer.println("State History Tree:\n"); //$NON-NLS-1$
+        writer.println(sht.toString());
+        writer.println("Average node utilization: " //$NON-NLS-1$
+                + this.getAverageNodeUsage());
+        writer.println(""); //$NON-NLS-1$
+
+        sht.debugPrintFullTree(writer, printIntervals);
+    }
+}
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/backend/historytree/ThreadedHistoryTreeBackend.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/backend/historytree/ThreadedHistoryTreeBackend.java
new file mode 100644 (file)
index 0000000..a66cca1
--- /dev/null
@@ -0,0 +1,177 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * Copyright (c) 2010, 2011 École Polytechnique de Montréal
+ * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
+ * 
+ * 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
+ * 
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.statesystem.backend.historytree;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+
+import org.eclipse.linuxtools.tmf.core.statesystem.TimeRangeException;
+import org.eclipse.linuxtools.tmf.core.statevalue.ITmfStateValue;
+import org.eclipse.linuxtools.tmf.core.statevalue.TmfStateValue;
+
+/**
+ * Variant of the HistoryTreeBackend which runs all the interval-insertion logic
+ * in a separate thread.
+ * 
+ * @author alexmont
+ * 
+ */
+public class ThreadedHistoryTreeBackend extends HistoryTreeBackend implements
+        Runnable {
+
+    /*
+     * From superclass:
+     * 
+     * protected final StateHistoryTree sht;
+     */
+
+    private BlockingQueue<HTInterval> intervalQueue;
+    private final Thread shtThread;
+
+    /**
+     * New state history constructor
+     * 
+     * Note that it usually doesn't make sense to use a Threaded HT if you're
+     * opening an existing state-file, but you know what you're doing...
+     * 
+     * @param newStateFile
+     *            The name of the history file that will be created. Should end
+     *            in ".ht"
+     * @param blockSize
+     *            The size of the blocks in the file
+     * @param maxChildren
+     *            The maximum number of children allowed for each core node
+     * @param startTime
+     *            The earliest timestamp stored in the history
+     * @param queueSize
+     *            The size of the interval insertion queue. 2000 - 10000 usually
+     *            works well
+     * @throws IOException
+     *             If there was a problem opening the history file for writing
+     */
+    public ThreadedHistoryTreeBackend(File newStateFile, int blockSize,
+            int maxChildren, long startTime, int queueSize) throws IOException {
+        super(newStateFile, blockSize, maxChildren, startTime);
+
+        intervalQueue = new ArrayBlockingQueue<HTInterval>(queueSize);
+        shtThread = new Thread(this, "History Tree Thread"); //$NON-NLS-1$
+        shtThread.start();
+    }
+
+    /**
+     * New State History constructor. This version provides default values for
+     * blockSize and maxChildren.
+     * 
+     * @param newStateFile
+     *            The name of the history file that will be created. Should end
+     *            in ".ht"
+     * @param startTime
+     *            The earliest timestamp stored in the history
+     * @param queueSize
+     *            The size of the interval insertion queue. 2000 - 10000 usually
+     *            works well
+     * @throws IOException
+     *             If there was a problem opening the history file for writing
+     */
+    public ThreadedHistoryTreeBackend(File newStateFile, long startTime,
+            int queueSize) throws IOException {
+        super(newStateFile, startTime);
+
+        intervalQueue = new ArrayBlockingQueue<HTInterval>(queueSize);
+        shtThread = new Thread(this, "History Tree Thread"); //$NON-NLS-1$
+        shtThread.start();
+    }
+
+    /*
+     * The Threaded version does not specify an "existing file" constructor,
+     * since the history is already built (and we only use the other thread
+     * during building). Just use a plain HistoryTreeProvider in this case.
+     * 
+     * TODO but what about streaming??
+     */
+
+    @Override
+    public void insertPastState(long stateStartTime, long stateEndTime,
+            int quark, ITmfStateValue value) throws TimeRangeException {
+        /*
+         * Here, instead of directly inserting the elements in the History Tree
+         * underneath, we'll put them in the Queue. They will then be taken and
+         * processed by the other thread executing the run() method.
+         */
+        HTInterval interval = new HTInterval(stateStartTime, stateEndTime,
+                quark, (TmfStateValue) value);
+        try {
+            intervalQueue.put(interval);
+        } catch (InterruptedException e) {
+            /* We should not get interrupted here */
+            System.out.println("State system got interrupted!"); //$NON-NLS-1$
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void finishedBuilding(long endTime) throws TimeRangeException {
+        /*
+         * We need to commit everything in the History Tree and stop the
+         * standalone thread before returning to the StateHistorySystem. (SHS
+         * will then write the Attribute Tree to the file, that must not happen
+         * at the same time we are writing the last nodes!)
+         */
+
+        /*
+         * Send the "poison pill" in the queue, then wait for the HT to finish
+         * its closeTree()
+         */
+        try {
+            intervalQueue.put(new HTInterval(-1, endTime, -1,
+                    TmfStateValue.nullValue()));
+            shtThread.join();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        return;
+    }
+
+    @Override
+    public void run() {
+        if (intervalQueue == null) {
+            System.err.println("Cannot start the storage backend without its interval queue."); //$NON-NLS-1$
+            return;
+        }
+        HTInterval currentInterval;
+        try {
+            currentInterval = intervalQueue.take();
+            while (currentInterval.getStartTime() != -1) {
+                /* Send the interval to the History Tree */
+                sht.insertInterval(currentInterval);
+                currentInterval = intervalQueue.take();
+            }
+            assert (currentInterval.getAttribute() == -1);
+            /*
+             * We've been told we're done, let's write down everything and quit
+             */
+            sht.closeTree();
+            return;
+        } catch (InterruptedException e) {
+            /* We've been interrupted abnormally */
+            System.out.println("State History Tree interrupted!"); //$NON-NLS-1$
+            e.printStackTrace();
+        } catch (TimeRangeException e) {
+            /* This also should not happen */
+            e.printStackTrace();
+        }
+    }
+
+}
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/helpers/HistoryBuilder.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/helpers/HistoryBuilder.java
new file mode 100644 (file)
index 0000000..5e6a3c6
--- /dev/null
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * Copyright (c) 2010, 2011 École Polytechnique de Montréal
+ * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
+ * 
+ * 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
+ * 
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.statesystem.helpers;
+
+import java.io.IOException;
+
+import org.eclipse.linuxtools.tmf.core.statesystem.StateHistorySystem;
+import org.eclipse.linuxtools.tmf.core.statesystem.StateSystem;
+
+/**
+ * This is the high-level wrapper around the State History and its input and
+ * storage plugins. Just create the object using the constructor then .run()
+ * 
+ * You can use one HistoryBuilder and it will instantiate everything underneath.
+ * If you need more fine-grained control you can still ignore this and
+ * instantiate everything manually.
+ * 
+ * @author alexmont
+ * 
+ */
+public class HistoryBuilder implements Runnable {
+
+    private final IStateChangeInput sci;
+    private final StateSystem ss;
+    private final IStateHistoryBackend hb;
+
+    private final Thread siThread;
+
+    /**
+     * Instantiate a new HistoryBuilder helper.
+     * 
+     * @param stateChangeInput
+     *            The input plugin to use. This is required.
+     * @param backend
+     *            The backend storage to use. Use "null" here if you want a
+     *            state system with no history.
+     * @throws IOException
+     *             Is thrown if anything went wrong (usually with the storage
+     *             backend)
+     */
+    public HistoryBuilder(IStateChangeInput stateChangeInput,
+            IStateHistoryBackend backend) throws IOException {
+        assert (stateChangeInput != null);
+        /* "backend" can be null, this implies no history */
+        sci = stateChangeInput;
+
+        if (backend == null) {
+            hb = null;
+            ss = new StateSystem();
+        } else {
+            hb = backend;
+            ss = new StateHistorySystem(hb, true);
+        }
+
+        sci.assignTargetStateSystem(ss);
+        siThread = new Thread(sci, "Input Plugin"); //$NON-NLS-1$
+    }
+
+    @Override
+    public void run() {
+        siThread.start();
+
+        try {
+            siThread.join();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+            assert (false);
+        }
+    }
+
+    /**
+     * Return the StateSystem (or StateHistorySystem) object that was created by
+     * this builder. You will need this reference to run queries.
+     * 
+     * @return The StateSystem that was generated
+     */
+    public StateSystem getSS() {
+        return ss;
+    }
+
+}
\ No newline at end of file
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/helpers/IStateChangeInput.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/helpers/IStateChangeInput.java
new file mode 100644 (file)
index 0000000..dfd7a29
--- /dev/null
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * Copyright (c) 2010, 2011 École Polytechnique de Montréal
+ * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
+ * 
+ * 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
+ * 
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.statesystem.helpers;
+
+import org.eclipse.linuxtools.tmf.core.statesystem.StateSystem;
+
+/**
+ * This is the interface used to define the "state change input", which is the
+ * main type of input that goes in the state system.
+ * 
+ * Usually a state change input, also called "state provider" is the piece of
+ * the pipeline which converts trace events to state changes.
+ * 
+ * @author alexmont
+ * 
+ */
+public interface IStateChangeInput extends Runnable {
+
+    /**
+     * Return the start time of this "state change input", which is normally the
+     * start time of the originating trace (or it can be the time of the first
+     * state-changing event).
+     * 
+     * @return The start time
+     */
+    public long getStartTime();
+
+    /**
+     * Assign the target state system where this SCI will insert its state
+     * changes. Because of dependencies issues, this can normally not be done at
+     * the constructor.
+     * 
+     * This needs to be called before .run()!
+     * 
+     * @param ss
+     */
+    public void assignTargetStateSystem(StateSystem ss);
+
+    /**
+     * Return the State System that was assigned to this SCI.
+     * 
+     * @return The target state system
+     */
+    public StateSystem getStateSystem();
+
+}
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/helpers/IStateHistoryBackend.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statesystem/helpers/IStateHistoryBackend.java
new file mode 100644 (file)
index 0000000..26d3263
--- /dev/null
@@ -0,0 +1,149 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * Copyright (c) 2010, 2011 École Polytechnique de Montréal
+ * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
+ * 
+ * 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
+ * 
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.statesystem.helpers;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.PrintWriter;
+import java.util.List;
+
+import org.eclipse.linuxtools.tmf.core.interval.ITmfStateInterval;
+import org.eclipse.linuxtools.tmf.core.statesystem.AttributeNotFoundException;
+import org.eclipse.linuxtools.tmf.core.statesystem.TimeRangeException;
+import org.eclipse.linuxtools.tmf.core.statevalue.ITmfStateValue;
+
+/**
+ * The main difference between StateSystem and StateHistorySystem is that SHS
+ * allows 'seeking' back in time to reload a Current State at a previous time.
+ * "How to go back in time" is defined by the implementation of the
+ * HistoryBackend.
+ * 
+ * A StateHistorySystem contains one and only one HistoryBackend. If you want to
+ * use a paradigm with more than one provider (eg. more or less precision
+ * depending on what's asked by the user), implement one wrapper HistoryBackend
+ * which can then contain your 2-3 other backends underneath.
+ * 
+ * @author alexmont
+ * 
+ */
+public interface IStateHistoryBackend {
+
+    /**
+     * Get the start time of this state history. This is usually the same as the
+     * start time of the originating trace.
+     * 
+     * @return The start time
+     */
+    public long getStartTime();
+
+    /**
+     * Get the current end time of the state history. It will change as the
+     * history is being built.
+     * 
+     * @return The end time
+     */
+    public long getEndTime();
+
+    /**
+     * Main method to insert state intervals into the history.
+     * 
+     * @param stateStartTime
+     *            The start time of the interval
+     * @param stateEndTime
+     *            The end time of the interval
+     * @param quark
+     *            The quark of the attribute this interval refers to
+     * @param value
+     *            The StateValue represented by this interval
+     * @throws TimeRangeException
+     *             If the start or end time are invalid
+     */
+    // FIXME change to IStateInterval?
+    public void insertPastState(long stateStartTime, long stateEndTime,
+            int quark, ITmfStateValue value) throws TimeRangeException;
+
+    /**
+     * Indicate to the provider that we are done building the history (so it can
+     * close off, stop threads, etc.)
+     * 
+     * @param endTime
+     *            The end time to assign to this state history. It could be
+     *            farther in time than the last state inserted, for example.
+     * @throws TimeRangeException
+     *             If the requested time makes no sense.
+     */
+    public void finishedBuilding(long endTime) throws TimeRangeException;
+
+    /**
+     * It is the responsibility of the backend to define where to save the
+     * Attribute Tree (since it's only useful to "reopen" an Attribute Tree if
+     * we have the matching History).
+     * 
+     * This method defines where to read for the attribute tree when opening an
+     * already-existing history. Refer to the file format documentation.
+     * 
+     * @return A FileInputStream object pointing to the correct file/location in
+     *         the file where to read the attribute tree information.
+     */
+    public FileInputStream supplyAttributeTreeReader();
+
+    // FIXME change to FOS too?
+    public File supplyAttributeTreeWriterFile();
+
+    public long supplyAttributeTreeWriterFilePosition();
+
+    /**
+     * @name Query methods
+     */
+
+    /**
+     * Complete "give me the state at a given time" method 'currentStateInfo' is
+     * an "out" parameter, that is, write to it the needed information and
+     * return. DO NOT 'new' currentStateInfo, it will be lost and nothing will
+     * be returned!
+     * 
+     * @param currentStateInfo
+     *            List of StateValues (index == quark) to fill up
+     * @param t
+     *            Target timestamp of the query
+     */
+    public void doQuery(List<ITmfStateInterval> currentStateInfo, long t)
+            throws TimeRangeException;
+
+    /**
+     * Some providers might want to specify a different way to obtain just a
+     * single StateValue instead of updating the whole list. If the method to
+     * use is the same, then feel free to just implement this as a wrapper using
+     * doQuery().
+     * 
+     * @param t
+     *            The target timestamp of the query.
+     * @param attributeQuark
+     *            The single attribute for which you want the state interval
+     * @return The state interval matching this timestamp/attribute pair
+     * @throws TimeRangeException
+     *             If the timestamp was invalid
+     * @throws AttributeNotFoundException
+     *             If the quark was invalid
+     */
+    public ITmfStateInterval doSingularQuery(long t, int attributeQuark)
+            throws TimeRangeException, AttributeNotFoundException;
+
+    /**
+     * Debug method to print the contents of the history backend.
+     * 
+     * @param writer
+     *            The PrintWriter where to write the output
+     */
+    public void debugPrint(PrintWriter writer);
+}
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statevalue/ITmfStateValue.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statevalue/ITmfStateValue.java
new file mode 100644 (file)
index 0000000..d078e3a
--- /dev/null
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * 
+ * 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
+ ******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.statevalue;
+
+
+/**
+ * This is the interface for using state values and reading their contents.
+ * 
+ * @author alexmont
+ * 
+ */
+public interface ITmfStateValue {
+
+    /**
+     * Each implementation has to supply a "type" number. This will get written
+     * as-is in the History file to recognize the type, so it needs to be unique
+     * 
+     * @return The unique "int8" assigned to this state value type
+     */
+    public byte getType();
+
+    /**
+     * Only "null values" should return true here
+     * 
+     * @return True if this type of SV is considered "null", false if it
+     *         contains a real value.
+     */
+    public boolean isNull();
+
+    /**
+     * Read the contained value as an 'int' primitive
+     * 
+     * @return The integer contained in the state value
+     * @throws StateValueTypeException
+     *             If the contained value cannot be read as an integer
+     */
+    public int unboxInt() throws StateValueTypeException;
+
+    /**
+     * Read the contained value as a String
+     * 
+     * @return The String contained in the state value
+     * @throws StateValueTypeException
+     *             If the contained value cannot be read as a String
+     */
+    public String unboxStr() throws StateValueTypeException;
+}
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statevalue/IntegerStateValue.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statevalue/IntegerStateValue.java
new file mode 100644 (file)
index 0000000..5b7cc65
--- /dev/null
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * Copyright (c) 2010, 2011 École Polytechnique de Montréal
+ * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
+ * 
+ * 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
+ * 
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.statevalue;
+
+/**
+ * A state value containing a simple integer.
+ * 
+ * @author alexmont
+ *
+ */
+final class IntegerStateValue extends TmfStateValue {
+
+    private final int valueInt;
+
+    public IntegerStateValue(int valueAsInt) {
+        this.valueInt = valueAsInt;
+    }
+
+    @Override
+    public byte getType() {
+        return 0;
+    }
+
+    @Override
+    public boolean isNull() {
+        return false;
+    }
+
+    @Override
+    public Integer getValue() {
+        return valueInt;
+    }
+
+    @Override
+    public byte[] toByteArray() {
+        return null;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%3d", valueInt); //$NON-NLS-1$
+    }
+}
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statevalue/NullStateValue.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statevalue/NullStateValue.java
new file mode 100644 (file)
index 0000000..8afcce6
--- /dev/null
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * Copyright (c) 2010, 2011 École Polytechnique de Montréal
+ * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
+ * 
+ * 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
+ * 
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.statevalue;
+
+/**
+ * A state value that contains no particular value. It is sometimes needed over
+ * a "null" reference, since we avoid NPE's this way.
+ * 
+ * It can also be read either as a String ("nullValue") or an Integer (-1).
+ * 
+ * @author alexmont
+ *
+ */
+final class NullStateValue extends TmfStateValue {
+
+    @Override
+    public byte getType() {
+        return -1;
+    }
+
+    @Override
+    public boolean isNull() {
+        return true;
+    }
+
+    @Override
+    public Object getValue() {
+        return null;
+    }
+
+    @Override
+    public byte[] toByteArray() {
+        return null;
+    }
+
+    @Override
+    public String toString() {
+        return "nullValue"; //$NON-NLS-1$
+    }
+}
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statevalue/StateValueTypeException.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statevalue/StateValueTypeException.java
new file mode 100644 (file)
index 0000000..ad010e2
--- /dev/null
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * Copyright (c) 2010, 2011 École Polytechnique de Montréal
+ * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
+ * 
+ * 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
+ * 
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.statevalue;
+
+/**
+ * The StateValue is a wrapper around the different type of values that can be
+ * used and stored in the state system and history. "Unboxing" the value means
+ * retrieving the base type (int, String, etc.) inside it.
+ * 
+ * This exception is thrown if the user tries to unbox a StateValue with an
+ * incorrect type (for example, tries to read a String value as an Int).
+ * 
+ * @author alexmont
+ *
+ */
+public class StateValueTypeException extends Exception {
+
+    /**
+     * 
+     */
+    private static final long serialVersionUID = -4548793451746144513L;
+
+}
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statevalue/StringStateValue.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statevalue/StringStateValue.java
new file mode 100644 (file)
index 0000000..4be757c
--- /dev/null
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * Copyright (c) 2010, 2011 École Polytechnique de Montréal
+ * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
+ * 
+ * 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
+ * 
+ *******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.statevalue;
+
+/**
+ * A state value containing a variable-sized string
+ * 
+ * @author alexmont
+ *
+ */
+final class StringStateValue extends TmfStateValue {
+
+    private final String valueStr;
+
+    public StringStateValue(String valueAsString) {
+        assert (valueAsString != null);
+        this.valueStr = valueAsString;
+    }
+
+    @Override
+    public byte getType() {
+        return 1;
+    }
+
+    @Override
+    public boolean isNull() {
+        return false;
+    }
+
+    @Override
+    public String getValue() {
+        return valueStr;
+    }
+
+    @Override
+    public byte[] toByteArray() {
+        return valueStr.getBytes();
+    }
+
+    @Override
+    public String toString() {
+        return valueStr;
+    }
+}
diff --git a/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statevalue/TmfStateValue.java b/org.eclipse.linuxtools.tmf.core/src/org/eclipse/linuxtools/tmf/core/statevalue/TmfStateValue.java
new file mode 100644 (file)
index 0000000..53a0aac
--- /dev/null
@@ -0,0 +1,162 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Ericsson
+ * Copyright (c) 2010, 2011 École Polytechnique de Montréal
+ * Copyright (c) 2010, 2011 Alexandre Montplaisir <alexandre.montplaisir@gmail.com>
+ * 
+ * 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
+ * 
+ ******************************************************************************/
+
+package org.eclipse.linuxtools.tmf.core.statevalue;
+
+
+/**
+ * This is the wrapper class that exposes the different types of 'state values'
+ * available to use in the State System.
+ * 
+ * This also defines how these values are to be stored in the History Tree. For
+ * example, we can save numerical values as integers instead of arrays of
+ * 1-digit characters.
+ * 
+ * For now the two available types are either int or String.
+ * 
+ * @author alexmont
+ * 
+ */
+public abstract class TmfStateValue implements ITmfStateValue {
+
+    /**
+     * Retrieve directly the value object contained within. Implementing
+     * subclasses may limit the return type here.
+     * 
+     * It's protected, since we do not want to expose this directly in the
+     * public API (and require all its users to manually cast to the right
+     * types). All accesses to the values should go through the "unbox-"
+     * methods.
+     * 
+     * @return The underneath object assigned to this state value.
+     */
+    protected abstract Object getValue();
+
+    /**
+     * Specify how to "serialize" this value when writing it to a file.
+     * Alternatively you can return "null" here if you do not need a byte-array
+     * indirection (the getValue will get written as-in instead of the offset in
+     * the file block)
+     */
+    public abstract byte[] toByteArray();
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (!(other instanceof TmfStateValue)) {
+            return false;
+        }
+
+        /* If both types are different they're necessarily not equal */
+        if (this.getType() != ((TmfStateValue) other).getType()) {
+            return false;
+        }
+
+        /*
+         * This checks for the case where we'd compare two null values (and so
+         * avoid a NPE below)
+         */
+        if (this.isNull()) {
+            return true;
+        }
+
+        /* The two are valid and comparable, let's compare them */
+        return this.getValue().equals(((TmfStateValue) other).getValue());
+    }
+
+    @Override
+    public int hashCode() {
+        if (this.isNull()) {
+            return 0;
+        }
+        return this.getValue().hashCode();
+    }
+
+    /**
+     * Return the max size that a variable-length state value can have when
+     * serialized.
+     * 
+     * @return The maximum size in bytes
+     */
+    public static int getStateValueMaxSize() {
+        return Byte.MAX_VALUE;
+    }
+
+    /*
+     * Since all "null state values" are the same, we only need one copy in
+     * memory.
+     */
+    private static TmfStateValue nullValue = new NullStateValue();
+
+    /**
+     * Return an instance of a "null" value. Only one copy exists in memory.
+     * 
+     * @return A null value
+     */
+    public final static TmfStateValue nullValue() {
+        return nullValue;
+    }
+
+    /**
+     * Factory constructor for Integer state values
+     * 
+     * @param intValue The integer value to contain
+     * @return The newly-created TmfStateValue object
+     */
+    public static TmfStateValue newValueInt(int intValue) {
+        if (intValue == -1) {
+            return nullValue();
+        }
+        return new IntegerStateValue(intValue);
+    }
+
+    /**
+     * Factory constructor for String state values
+     * 
+     * @param strValue The string value to contain
+     * @return The newly-create TmfStateValue object
+     */
+    public static TmfStateValue newValueString(String strValue) {
+        if (strValue == null) {
+            return nullValue();
+        }
+        return new StringStateValue(strValue);
+    }
+
+    @Override
+    public int unboxInt() throws StateValueTypeException {
+        if (this.isNull()) {
+            /* Int value expected, return "-1" instead */
+            return -1;
+        }
+
+        if (this.getType() != 0) { /* 0 = int type */
+            throw new StateValueTypeException();
+        }
+        return (Integer) this.getValue();
+    }
+
+    @Override
+    public String unboxStr() throws StateValueTypeException {
+        if (this.isNull()) {
+            /* String value expected, return "nullValue" instead */
+            return "nullValue"; //$NON-NLS-1$
+        }
+
+        if (this.getType() != 1) { /* 1 = string type */
+            throw new StateValueTypeException();
+        }
+        return (String) this.getValue();
+    }
+}
This page took 0.071166 seconds and 5 git commands to generate.