ss: Add class to serialize/deserialize a statedump
authorAlexandre Montplaisir <alexmonthy@efficios.com>
Fri, 7 Jul 2017 20:58:51 +0000 (16:58 -0400)
committerAlexandre Montplaisir <alexmonthy@efficios.com>
Fri, 7 Jul 2017 20:58:51 +0000 (16:58 -0400)
This will allow saving and restoring the equivalent of an
"ongoing state", to serve as a statedump for trimmed traces
for example.

Change-Id: I95581bfb746eb8579aaf669ca3dccd4663137d67
Signed-off-by: Philippe Proulx <pproulx@efficios.com>
Signed-off-by: Alexandre Montplaisir <alexmonthy@efficios.com>
statesystem/org.eclipse.tracecompass.statesystem.core.tests/src/org/eclipse/tracecompass/statesystem/core/tests/StatedumpTest.java [new file with mode: 0644]
statesystem/org.eclipse.tracecompass.statesystem.core/META-INF/MANIFEST.MF
statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/statesystem/core/Statedump.java [new file with mode: 0644]

diff --git a/statesystem/org.eclipse.tracecompass.statesystem.core.tests/src/org/eclipse/tracecompass/statesystem/core/tests/StatedumpTest.java b/statesystem/org.eclipse.tracecompass.statesystem.core.tests/src/org/eclipse/tracecompass/statesystem/core/tests/StatedumpTest.java
new file mode 100644 (file)
index 0000000..57df264
--- /dev/null
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * 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.tracecompass.statesystem.core.tests;
+
+import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.io.FileUtils;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.tracecompass.statesystem.core.Statedump;
+import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue;
+import org.eclipse.tracecompass.statesystem.core.statevalue.TmfStateValue;
+import org.junit.Test;
+
+/**
+ * Tests for {@link Statedump}.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class StatedumpTest {
+
+    /**
+     * Test that a manually-written statedump is serialized then read correctly.
+     */
+    @Test
+    public void testSimpleStatedump() {
+        Path dir = null;
+        try {
+            dir = checkNotNull(Files.createTempDirectory("ss-serialization-test"));
+            String ssid = "test-ssid";
+            final int nbAttributes = 6;
+            final int version = 0;
+
+            List<String @NonNull []> initialAttributes = Arrays.asList(
+                    new String[] { "Threads" },
+                    new String[] { "Threads", "1000" },
+                    new String[] { "Threads", "1000", "Status" },
+                    new String[] { "Threads", "2000" },
+                    new String[] { "Threads", "2000", "Status" },
+                    new String[] { "Threads", "2000", "PPID" });
+
+            List<@NonNull ITmfStateValue> initialValues = Arrays.asList(
+                    TmfStateValue.nullValue(),
+                    TmfStateValue.nullValue(),
+                    TmfStateValue.newValueString("Running"),
+                    TmfStateValue.nullValue(),
+                    TmfStateValue.newValueInt(1),
+                    TmfStateValue.newValueLong(1000L));
+
+            Statedump statedump = new Statedump(initialAttributes, initialValues, version);
+            statedump.dumpState(dir, ssid);
+
+            Statedump results = Statedump.loadState(dir, ssid);
+            assertNotNull(results);
+
+            assertEquals(version, results.getVersion());
+
+            List<String[]> newAttributes = results.getAttributes();
+            List<ITmfStateValue> newValues = results.getStates();
+            assertEquals(nbAttributes, newAttributes.size());
+            assertEquals(nbAttributes, newValues.size());
+
+            for (int i = 0; i < nbAttributes; i++) {
+                assertArrayEquals(initialAttributes.get(i), newAttributes.get(i));
+                assertEquals(initialValues.get(i), newValues.get(i));
+            }
+
+        } catch (IOException e) {
+            fail(e.getMessage());
+
+        } finally {
+            if (dir != null) {
+                FileUtils.deleteQuietly(dir.toFile());
+            }
+        }
+    }
+}
index 81f3d3cb3f8c92793f25e4a179750e0902aeb6ae..8a729c7a7400860f9d3c6bbd8969e20a151678d0 100644 (file)
@@ -25,4 +25,5 @@ Export-Package: org.eclipse.tracecompass.internal.provisional.statesystem.core.s
 Import-Package: com.google.common.annotations;version="15.0.0",
  com.google.common.base,
  com.google.common.cache,
- com.google.common.collect;version="12.0.0"
+ com.google.common.collect;version="12.0.0",
+ org.json
diff --git a/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/statesystem/core/Statedump.java b/statesystem/org.eclipse.tracecompass.statesystem.core/src/org/eclipse/tracecompass/statesystem/core/Statedump.java
new file mode 100644 (file)
index 0000000..4db3c24
--- /dev/null
@@ -0,0 +1,581 @@
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc. and others
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.statesystem.core;
+
+import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.internal.provisional.datastore.core.serialization.ISafeByteBufferReader;
+import org.eclipse.tracecompass.internal.provisional.datastore.core.serialization.ISafeByteBufferWriter;
+import org.eclipse.tracecompass.internal.provisional.datastore.core.serialization.SafeByteBufferFactory;
+import org.eclipse.tracecompass.internal.provisional.statesystem.core.statevalue.CustomStateValue;
+import org.eclipse.tracecompass.internal.statesystem.core.Activator;
+import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException;
+import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
+import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue;
+import org.eclipse.tracecompass.statesystem.core.statevalue.TmfStateValue;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Wrapper object representing a full query, along with its corresponding
+ * attributes. It allows to reconstruct an initial state from scratch.
+ *
+ * @author Alexandre Montplaisir
+ * @author Philippe Proulx
+ * @since 2.2
+ */
+@NonNullByDefault
+public class Statedump {
+
+    /** File format version. Bump if the format changes */
+    private static final int STATEDUMP_FORMAT_VERSION = 1;
+
+    private static final String STATEDUMP_DIRECTORY = ".tc-states"; //$NON-NLS-1$
+    private static final String FILE_SUFFIX = ".statedump.json"; //$NON-NLS-1$
+
+    private final List<String[]> fAttributes;
+    private final List<ITmfStateValue> fStates;
+    private final int fStatedumpVersion;
+
+    /**
+     * Baseline constructor. Builds a statedump by passing elements directly.
+     *
+     * @param attributes
+     *            The list of state system attributes
+     * @param states
+     *            The corresponding states. The indices should match the
+     *            previous list.
+     * @param version
+     *            Version of the statedump
+     * @throws IllegalArgumentException
+     *             If the two arrays don't have the same size
+     */
+    public Statedump(List<String[]> attributes, List<ITmfStateValue> states, int version) {
+        if (attributes.size() != states.size()) {
+            throw new IllegalArgumentException("Both lists should have the same number of elements"); //$NON-NLS-1$
+        }
+        fAttributes = ImmutableList.copyOf(attributes);
+        fStates = ImmutableList.copyOf(states);
+        fStatedumpVersion = version;
+    }
+
+    /**
+     * "Online" constructor. Builds a statedump from a given state system and
+     * timestamp.
+     *
+     * @param ss
+     *            The state system for which to build the state dump
+     * @param timestamp
+     *            The timestamp at which to query the state to dump
+     * @param version
+     *            Version of the statedump
+     */
+    public Statedump(ITmfStateSystem ss, long timestamp, int version) {
+        List<ITmfStateInterval> fullQuery;
+        try {
+            fullQuery = ss.queryFullState(timestamp);
+        } catch (StateSystemDisposedException e1) {
+            fAttributes = Collections.EMPTY_LIST;
+            fStates = Collections.EMPTY_LIST;
+            fStatedumpVersion = -1;
+            return;
+        }
+
+        ImmutableList.Builder<String[]> attributesBuilder = ImmutableList.builder();
+        for (int quark = 0; quark < ss.getNbAttributes(); quark++) {
+            attributesBuilder.add(ss.getFullAttributePathArray(quark));
+        }
+        fAttributes = attributesBuilder.build();
+
+        List<ITmfStateValue> states = fullQuery.stream()
+                .map(ITmfStateInterval::getStateValue)
+                .collect(Collectors.toList());
+        fStates = ImmutableList.copyOf(states);
+
+        fStatedumpVersion = version;
+    }
+
+    /**
+     * Get the list of attributes of this state dump.
+     *
+     * @return The attributes
+     */
+    public List<String[]> getAttributes() {
+        return fAttributes;
+    }
+
+    /**
+     * Get the state values of this state dump.
+     *
+     * @return The state values
+     */
+    public List<ITmfStateValue> getStates() {
+        return fStates;
+    }
+
+    /**
+     * Get the version of this statedump. Can be used to consider if a statedump
+     * should be read or not if the analysis changed since it was written.
+     *
+     * @return The statedump's version
+     */
+    public int getVersion() {
+        return fStatedumpVersion;
+    }
+
+    /**
+     * Save this statedump at the given location.
+     *
+     * @param parentPath
+     *            The location where to save the statedump file, usually in or
+     *            close to its corresponding trace. It will be put under a Trace
+     *            Compass-specific sub-directory.
+     * @param ssid
+     *            The state system ID of the state system we are saving. This
+     *            will be used for restoration.
+     * @throws IOException
+     *             If there are problems creating or writing to the target
+     *             directory
+     */
+    public void dumpState(Path parentPath, String ssid) throws IOException {
+        /* Create directory if it does not exist */
+        Path sdPath = parentPath.resolve(STATEDUMP_DIRECTORY);
+        if (!Files.exists(sdPath)) {
+            Files.createDirectory(sdPath);
+        }
+
+        /* Create state dump file */
+        String fileName = ssid + FILE_SUFFIX;
+        Path filePath = sdPath.resolve(fileName);
+        if (Files.exists(filePath)) {
+            Files.delete(filePath);
+        }
+        Files.createFile(filePath);
+
+        JSONObject root = new JSONObject();
+
+        try (Writer bw = Files.newBufferedWriter(filePath, Charsets.UTF_8)) {
+            /* Create the root object */
+            root.put(Serialization.FORMAT_VERSION_KEY, STATEDUMP_FORMAT_VERSION);
+            root.put(Serialization.ID_KEY, ssid);
+            root.put(Serialization.STATEDUMP_VERSION_KEY, getVersion());
+
+            /* Create the root state node */
+            JSONObject rootNode = new JSONObject();
+            rootNode.put(Serialization.CHILDREN_KEY, new JSONObject());
+            root.put(Serialization.STATE_KEY, rootNode);
+
+            /* Insert all the paths, types, and values */
+            for (int i = 0; i < getAttributes().size(); i++) {
+                String[] attribute = getAttributes().get(i);
+                ITmfStateValue sv = getStates().get(i);
+
+                Serialization.insertFrom(rootNode, attribute, 0, sv);
+            }
+
+            bw.write(root.toString(2));
+
+        } catch (JSONException e) {
+            /*
+             * This should never happen. Any JSON exception means that there's a
+             * bug in this code.
+             */
+            throw new IllegalStateException(e);
+        }
+    }
+
+    /**
+     * Retrieve a previously-saved statedump.
+     *
+     * @param parentPath
+     *            The expected location of the statedump file. Like the
+     *            corresponding parameter in {@link #dumpState}, this is the
+     *            parent path of the TC-specific subdirectory.
+     * @param ssid
+     *            The ID of the state system to retrieve
+     * @return The corresponding de-serialized statedump. Returns null if there
+     *         are no statedump for this state system ID (or no statedump
+     *         directory at all).
+     */
+    public static @Nullable Statedump loadState(Path parentPath, String ssid) {
+        /* Find the state dump directory */
+        Path sdPath = parentPath.resolve(STATEDUMP_DIRECTORY);
+        if (!Files.isDirectory(sdPath)) {
+            return null;
+        }
+
+        /* Find the state dump file */
+        String fileName = ssid + FILE_SUFFIX;
+        Path filePath = sdPath.resolve(fileName);
+        if (!Files.exists(filePath)) {
+            return null;
+        }
+
+        try (InputStreamReader in = new InputStreamReader(Files.newInputStream(filePath, StandardOpenOption.READ))) {
+            BufferedReader bufReader = new BufferedReader(in);
+            String json = bufReader.lines().collect(Collectors.joining("\n")); //$NON-NLS-1$
+            JSONObject root = new JSONObject(json);
+
+            return Serialization.stateDumpFromJsonObject(root, ssid);
+        } catch (IOException | JSONException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Inner utility class for serialization-related values and methods.
+     */
+    private static final class Serialization {
+
+        private Serialization() {}
+
+        private static final String VALUE_KEY = "value"; //$NON-NLS-1$
+        private static final String TYPE_KEY = "type"; //$NON-NLS-1$
+        private static final String CHILDREN_KEY = "children"; //$NON-NLS-1$
+        private static final String STATE_KEY = "state"; //$NON-NLS-1$
+        private static final String ID_KEY = "id"; //$NON-NLS-1$
+        private static final String FORMAT_VERSION_KEY = "format-version"; //$NON-NLS-1$
+        private static final String STATEDUMP_VERSION_KEY = "statedump-version"; //$NON-NLS-1$
+        private static final String CUSTOM_TYPE = "custom"; //$NON-NLS-1$
+        private static final String DOUBLE_TYPE = "double"; //$NON-NLS-1$
+        private static final String INT_TYPE = "int"; //$NON-NLS-1$
+        private static final String LONG_TYPE = "long"; //$NON-NLS-1$
+        private static final String NULL_TYPE = "null"; //$NON-NLS-1$
+        private static final String STRING_TYPE = "string"; //$NON-NLS-1$
+        private static final String UNKNOWN_TYPE = "unknown"; //$NON-NLS-1$
+        private static final String DOUBLE_NAN = "nan"; //$NON-NLS-1$
+        private static final String DOUBLE_POS_INF = "+inf"; //$NON-NLS-1$
+        private static final String DOUBLE_NEG_INF = "-inf"; //$NON-NLS-1$
+
+        private static void insertStateValueInStateNode(JSONObject stateNode, ITmfStateValue stateValue) throws JSONException {
+
+            @NonNull String type;
+            Object value;
+
+            switch (stateValue.getType()) {
+            case CUSTOM:
+                type = CUSTOM_TYPE;
+                CustomStateValue customValue = (CustomStateValue) stateValue;
+
+                /* Custom state values are serialized via a ByteBuffer */
+                int size = customValue.getSerializedSize();
+                ByteBuffer buffer = ByteBuffer.allocate(size);
+                buffer.clear();
+                ISafeByteBufferWriter sbbw = SafeByteBufferFactory.wrapWriter(buffer, size);
+                customValue.serialize(sbbw);
+                byte[] serializedValue = buffer.array();
+                value = Base64.getEncoder().encodeToString(serializedValue);
+                break;
+            case DOUBLE:
+                type = DOUBLE_TYPE;
+                double doubleValue = stateValue.unboxDouble();
+
+                if (Double.isNaN(doubleValue)) {
+                    value = DOUBLE_NAN;
+                } else if (Double.isInfinite(doubleValue)) {
+                    if (doubleValue < 0) {
+                        value = DOUBLE_NEG_INF;
+                    } else {
+                        value = DOUBLE_POS_INF;
+                    }
+                }
+
+                value = doubleValue;
+                break;
+            case INTEGER:
+                type = INT_TYPE;
+                value = stateValue.unboxInt();
+                break;
+            case LONG:
+                type = LONG_TYPE;
+                value = stateValue.unboxLong();
+                break;
+            case NULL:
+                type = NULL_TYPE;
+                value = null;
+                break;
+            case STRING:
+                type = STRING_TYPE;
+                value = stateValue.unboxStr();
+                break;
+            default:
+                type = UNKNOWN_TYPE;
+                value = stateValue.toString();
+                break;
+            }
+
+            stateNode.put(TYPE_KEY, type);
+            if (value != null) {
+                stateNode.put(VALUE_KEY, value);
+            }
+        }
+
+        private static void insertFrom(JSONObject stateNode, String[] attr, int attrIndex, ITmfStateValue stateValue) throws JSONException {
+            if (attr.length == 0 || !stateNode.has(CHILDREN_KEY)) {
+                throw new IllegalStateException();
+            }
+
+            JSONObject nodeChildren = stateNode.getJSONObject(CHILDREN_KEY);
+            String curAttrElement = attr[attrIndex];
+
+            if (!nodeChildren.has(curAttrElement)) {
+                JSONObject newNode = new JSONObject();
+                newNode.put(CHILDREN_KEY, new JSONObject());
+                nodeChildren.put(curAttrElement, newNode);
+            }
+
+            JSONObject nearestChild = checkNotNull(nodeChildren.getJSONObject(curAttrElement));
+
+            if (attrIndex == attr.length - 1) {
+                /* end of recursion! */
+                insertStateValueInStateNode(nearestChild, stateValue);
+                return;
+            }
+
+            insertFrom(nearestChild, attr, attrIndex + 1, stateValue);
+        }
+
+        private static @Nullable ITmfStateValue stateValueFromJsonObject(JSONObject node) {
+            ITmfStateValue stateValue;
+
+            String type = node.optString(TYPE_KEY);
+            if (type == null) {
+                Activator.getDefault().logWarning("Missing \"" + TYPE_KEY + "\" property in state node object"); //$NON-NLS-1$ //$NON-NLS-2$
+                return null;
+            }
+
+            switch (type) {
+            case NULL_TYPE:
+                stateValue = TmfStateValue.nullValue();
+                break;
+
+            case INT_TYPE:
+            case LONG_TYPE:
+                long longValue;
+
+                try {
+                    longValue = node.getLong(VALUE_KEY);
+                } catch (JSONException e) {
+                    Activator.getDefault().logWarning(String.format("Invalid or missing \"%s\" property (expecting a number) in state node object", VALUE_KEY)); //$NON-NLS-1$
+                    return null;
+                }
+
+                if (type.equals(INT_TYPE)) {
+                    stateValue = TmfStateValue.newValueInt((int) longValue);
+                } else {
+                    stateValue = TmfStateValue.newValueLong(longValue);
+                }
+                break;
+
+            case DOUBLE_TYPE:
+                Double doubleValue;
+
+                Object nodeValue = node.opt(VALUE_KEY);
+                if (nodeValue == null) {
+                    Activator.getDefault().logWarning(String.format("Missing \"%s\" property in state node object", VALUE_KEY)); //$NON-NLS-1$
+                    return null;
+                }
+
+                if (nodeValue instanceof Double) {
+                    doubleValue = (Double) nodeValue;
+                } else if (nodeValue instanceof String) {
+                    String strValue = (String) nodeValue;
+                    switch (strValue) {
+                    case DOUBLE_NAN:
+                        doubleValue = Double.NaN;
+                        break;
+                    case DOUBLE_NEG_INF:
+                        doubleValue = Double.NEGATIVE_INFINITY;
+                        break;
+                    case DOUBLE_POS_INF:
+                        doubleValue = Double.POSITIVE_INFINITY;
+                        break;
+                    default:
+                        doubleValue = null;
+                    }
+                } else {
+                    doubleValue = null;
+                }
+
+                if (doubleValue == null) {
+                    Activator.getDefault().logWarning("Invalid \"" + VALUE_KEY + "\" property in state node object"); //$NON-NLS-1$ //$NON-NLS-2$
+                    return null;
+                }
+
+                stateValue = TmfStateValue.newValueDouble(doubleValue);
+                break;
+
+            case STRING_TYPE:
+            case UNKNOWN_TYPE:
+                String stringValue = node.optString(VALUE_KEY);
+                if (stringValue == null) {
+                    Activator.getDefault().logWarning("Invalid or missing \"" + VALUE_KEY + "\" property (expecting a string) in state node object"); //$NON-NLS-1$ //$NON-NLS-2$
+                    return null;
+                }
+
+                stateValue = TmfStateValue.newValueString(stringValue);
+                break;
+
+            case CUSTOM_TYPE:
+                String encodedValue = node.optString(VALUE_KEY);
+                if (encodedValue == null) {
+                    Activator.getDefault().logWarning("Invalid or missing \"" + VALUE_KEY + "\" property in custom type node object"); //$NON-NLS-1$ //$NON-NLS-2$
+                    return null;
+                }
+
+                byte[] serializedValue = Base64.getDecoder().decode(encodedValue);
+                ByteBuffer buffer = ByteBuffer.wrap(serializedValue);
+                ISafeByteBufferReader sbbr = SafeByteBufferFactory.wrapReader(buffer, serializedValue.length);
+                stateValue = CustomStateValue.readSerializedValue(sbbr);
+                break;
+
+            default:
+                Activator.getDefault().logWarning(String.format("Unknown \"" + TYPE_KEY + "\" property (\"" + type + "\") in state node object")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+                return null;
+            }
+
+            return stateValue;
+        }
+
+        private static boolean visitStateNode(JSONObject stateNode, List<String> attrStack,
+                List<String[]> attributes, List<ITmfStateValue> values) {
+            ITmfStateValue stateValue = null;
+            String[] attribute = attrStack.stream().toArray(String[]::new);
+
+            /* Ignore if it's the root node */
+            if (attribute.length > 0) {
+                stateValue = stateValueFromJsonObject(stateNode);
+
+                if (stateValue == null) {
+                    Activator.getDefault().logWarning("Cannot rebuild state value for attribute \"" + Arrays.toString(attribute) + '\"'); //$NON-NLS-1$
+                    return false;
+                }
+
+                /* Insert at the same position */
+                attributes.add(attribute);
+                values.add(stateValue);
+            }
+
+            if (stateNode.has(CHILDREN_KEY)) {
+                JSONObject childrenNode;
+
+                try {
+                    childrenNode = stateNode.getJSONObject(CHILDREN_KEY);
+                } catch (JSONException e) {
+                    Activator.getDefault().logWarning(String.format("At attribute \"%s\": expecting an object for the \"%s\" property", //$NON-NLS-1$
+                            Arrays.toString(attribute), CHILDREN_KEY));
+                    return false;
+                }
+
+                Iterator<String> keyIt = childrenNode.keys();
+
+                while (keyIt.hasNext()) {
+                    String key = keyIt.next();
+                    JSONObject childStateNode;
+
+                    try {
+                        childStateNode = checkNotNull(childrenNode.getJSONObject(key));
+                    } catch (JSONException e) {
+                        Activator.getDefault().logWarning(String.format("At attribute \"%s\": in \"%s\" node: expecting an object for the \"%s\" property", //$NON-NLS-1$
+                                Arrays.toString(attribute), CHILDREN_KEY, key));
+                        return false;
+                    }
+
+                    attrStack.add(key);
+
+                    if (!visitStateNode(childStateNode, attrStack, attributes, values)) {
+                        Activator.getDefault().logWarning(String.format("At attribute \"%s\": in \"%s\" node: failed to visit the \"%s\" property", //$NON-NLS-1$
+                                Arrays.toString(attribute), CHILDREN_KEY, key));
+                        return false;
+                    }
+
+                    attrStack.remove(attrStack.size() - 1);
+                }
+            }
+
+            return true;
+        }
+
+        private static @Nullable Statedump stateDumpFromJsonObject(JSONObject root, String expectedSsid) {
+            List<String[]> attributes = new ArrayList<>();
+            List<ITmfStateValue> values = new ArrayList<>();
+
+            int statedumpVersion;
+            JSONObject rootStateNode;
+            String keyToLookup = null;
+
+            try {
+                /* Read the file format version and ensure we can read it */
+                keyToLookup = FORMAT_VERSION_KEY;
+                int formatVersion = root.getInt(FORMAT_VERSION_KEY);
+
+                if (formatVersion != STATEDUMP_FORMAT_VERSION) {
+                    Activator.getDefault().logWarning("Skipping statedump file with unknown version " + formatVersion); //$NON-NLS-1$
+                    return null;
+                }
+
+                /*
+                 * Read state system ID property and check if it matches the
+                 * expected one.
+                 */
+                keyToLookup = ID_KEY;
+                String ssid = root.getString(ID_KEY);
+
+                if (!expectedSsid.equals(ssid)) {
+                    Activator.getDefault().logWarning("State system ID mismatch: expecting \"" + expectedSsid + "\", got \"" + ssid + "\""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+                    return null;
+                }
+
+                /* Read statedump version property */
+                keyToLookup = STATEDUMP_VERSION_KEY;
+                statedumpVersion = root.getInt(STATEDUMP_VERSION_KEY);
+
+
+                /* Read state property (root state node) */
+                keyToLookup = STATE_KEY;
+                rootStateNode = checkNotNull(root.getJSONObject(STATE_KEY));
+
+            } catch (JSONException e) {
+                Activator.getDefault().logWarning("Missing \"" + keyToLookup +"\" property in state dump (root) object"); //$NON-NLS-1$ //$NON-NLS-2$
+                return null;
+            }
+
+            if (!visitStateNode(rootStateNode, new ArrayList<String>(), attributes, values)) {
+                Activator.getDefault().logWarning("Failed to visit the root state node object"); //$NON-NLS-1$
+                return null;
+            }
+
+            return new Statedump(attributes, values, statedumpVersion);
+        }
+    }
+
+}
This page took 0.031802 seconds and 5 git commands to generate.