Added ability for commands to post properties to be used as params of other commands.

Starting to add monitor GUI.

Change-Id: I9fcf1568d0de27dfd1c19e875f8646fd731a1dfa
diff --git a/utils/stc/src/main/java/org/onlab/stc/MonitorLayout.java b/utils/stc/src/main/java/org/onlab/stc/MonitorLayout.java
new file mode 100644
index 0000000..1c0e731
--- /dev/null
+++ b/utils/stc/src/main/java/org/onlab/stc/MonitorLayout.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onlab.stc;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ * Computes scenario process flow layout for the Monitor GUI.
+ */
+public class MonitorLayout {
+
+    public static final int WIDTH = 210;
+    public static final int HEIGHT = 30;
+    public static final int W_GAP = 40;
+    public static final int H_GAP = 50;
+    public static final int SLOT_WIDTH = WIDTH + H_GAP;
+
+    private final Compiler compiler;
+    private final ProcessFlow flow;
+
+    private Map<Step, Box> boxes = Maps.newHashMap();
+
+    /**
+     * Creates a new shared process flow monitor.
+     *
+     * @param compiler scenario compiler
+     */
+    MonitorLayout(Compiler compiler) {
+        this.compiler = compiler;
+        this.flow = compiler.processFlow();
+
+        // Extract the flow and create initial bounding boxes.
+        boxes.put(null, new Box(null, 0));
+        flow.getVertexes().forEach(this::createBox);
+
+        computeLayout(null, 0, 1);
+    }
+
+    // Computes the graph layout giving preference to group associations.
+    private void computeLayout(Group group, int absoluteTier, int tier) {
+        Box box = boxes.get(group);
+
+        // Find all children of the group, or items with no group if at top.
+        Set<Step> children = group != null ? group.children() :
+                flow.getVertexes().stream().filter(s -> s.group() == null)
+                        .collect(Collectors.toSet());
+
+        children.forEach(s -> visit(s, absoluteTier, 1, group));
+
+        // Figure out what the group root vertexes are.
+        Set<Step> roots = findRoots(group);
+
+        // Compute the boxes for each of the roots.
+        roots.forEach(s -> updateBox(s, absoluteTier + 1, 1, group));
+
+        // Update the tier and depth of the group bounding box.
+        computeTiersAndDepth(group, box, absoluteTier, tier, children);
+
+        // Compute the minimum breadth of this group's bounding box.
+        computeBreadth(group, box, children);
+
+        // Compute child placements
+        computeChildPlacements(group, box, children);
+    }
+
+    // Updates the box for the specified step, given the tier number, which
+    // is relative to the parent.
+    private Box updateBox(Step step, int absoluteTier, int tier, Group group) {
+        Box box = boxes.get(step);
+        if (step instanceof Group) {
+            computeLayout((Group) step, absoluteTier, tier);
+        } else {
+            box.setTierAndDepth(absoluteTier, tier, 1, group);
+        }
+
+        // Follow the steps downstream of this one.
+        follow(step, absoluteTier + box.depth(), box.tier() + box.depth());
+        return box;
+    }
+
+    // Backwards follows edges leading towards the specified step to visit
+    // the source vertex and compute layout of those vertices that had
+    // sufficient number of visits to compute their tier.
+    private void follow(Step step, int absoluteTier, int tier) {
+        Group from = step.group();
+        flow.getEdgesTo(step).stream()
+                .filter(d -> visit(d.src(), absoluteTier, tier, from))
+                .forEach(d -> updateBox(d.src(), absoluteTier, tier, from));
+    }
+
+    // Visits each step, records maximum tier and returns true if this
+    // was the last expected visit.
+    private boolean visit(Step step, int absoluteTier, int tier, Group from) {
+        Box box = boxes.get(step);
+        return box.visitAndLatchMaxTier(absoluteTier, tier, from);
+    }
+
+    // Computes the absolute and relative tiers and the depth of the group
+    // bounding box.
+    private void computeTiersAndDepth(Group group, Box box,
+                                      int absoluteTier, int tier, Set<Step> children) {
+        int depth = children.stream().mapToInt(this::bottomMostTier).max().getAsInt();
+        box.setTierAndDepth(absoluteTier, tier, depth, group);
+    }
+
+    // Returns the bottom-most tier this step occupies relative to its parent.
+    private int bottomMostTier(Step step) {
+        Box box = boxes.get(step);
+        return box.tier() + box.depth();
+    }
+
+    // Computes breadth of the specified group.
+    private void computeBreadth(Group group, Box box, Set<Step> children) {
+        if (box.breadth() == 0) {
+            // Scan through all tiers and determine the maximum breadth of each.
+            IntStream.range(1, box.depth)
+                    .forEach(t -> computeTierBreadth(t, box, children));
+            box.latchBreadth(children.stream()
+                                     .mapToInt(s -> boxes.get(s).breadth())
+                                     .max().getAsInt());
+        }
+    }
+
+    // Computes tier width.
+    private void computeTierBreadth(int t, Box box, Set<Step> children) {
+        box.latchBreadth(children.stream().map(boxes::get)
+                                 .filter(b -> isSpanningTier(b, t))
+                                 .mapToInt(Box::breadth).sum());
+    }
+
+    // Computes the actual child box placements relative to the parent using
+    // the previously established tier, depth and breadth attributes.
+    private void computeChildPlacements(Group group, Box box,
+                                        Set<Step> children) {
+        // Order the root-nodes in alphanumeric order first.
+        List<Box> tierBoxes = Lists.newArrayList(boxesOnTier(1, children));
+        tierBoxes.sort((a, b) -> a.step().name().compareTo(b.step().name()));
+
+        // Place the boxes centered on the parent box; left to right.
+        int tierBreadth = tierBoxes.stream().mapToInt(Box::breadth).sum();
+        int slot = 1;
+        for (Box b : tierBoxes) {
+            b.updateCenter(1, slot(slot, tierBreadth));
+            slot += b.breadth();
+        }
+    }
+
+    // Returns the horizontal offset off the parent center.
+    private int slot(int slot, int tierBreadth) {
+        boolean even = tierBreadth % 2 == 0;
+        int multiplier = -tierBreadth / 2 + slot - 1;
+        return even ? multiplier * SLOT_WIDTH + SLOT_WIDTH / 2 : multiplier * SLOT_WIDTH;
+    }
+
+    // Returns a list of all child step boxes that start on the specified tier.
+    private List<Box> boxesOnTier(int tier, Set<Step> children) {
+        return boxes.values().stream()
+                .filter(b -> b.tier() == tier && children.contains(b.step()))
+                .collect(Collectors.toList());
+    }
+
+    // Determines whether the specified box spans, or occupies a tier.
+    private boolean isSpanningTier(Box b, int tier) {
+        return (b.depth() == 1 && b.tier() == tier) ||
+                (b.tier() <= tier && tier < b.tier() + b.depth());
+    }
+
+
+    // Determines roots of the specified group or of the entire graph.
+    private Set<Step> findRoots(Group group) {
+        Set<Step> steps = group != null ? group.children() : flow.getVertexes();
+        return steps.stream().filter(s -> isRoot(s, group)).collect(Collectors.toSet());
+    }
+
+    private boolean isRoot(Step step, Group group) {
+        if (step.group() != group) {
+            return false;
+        }
+
+        Set<Dependency> requirements = flow.getEdgesFrom(step);
+        return requirements.stream().filter(r -> r.dst().group() == group)
+                .collect(Collectors.toSet()).isEmpty();
+    }
+
+    /**
+     * Returns the bounding box for the specified step. If null is given, it
+     * returns the overall bounding box.
+     *
+     * @param step step or group; null for the overall bounding box
+     * @return bounding box
+     */
+    public Box get(Step step) {
+        return boxes.get(step);
+    }
+
+    /**
+     * Returns the bounding box for the specified step name. If null is given,
+     * it returns the overall bounding box.
+     *
+     * @param name name of step or group; null for the overall bounding box
+     * @return bounding box
+     */
+    public Box get(String name) {
+        return get(name == null ? null : compiler.getStep(name));
+    }
+
+    // Creates a bounding box for the specified step or group.
+    private void createBox(Step step) {
+        boxes.put(step, new Box(step, flow.getEdgesFrom(step).size()));
+    }
+
+    /**
+     * Bounding box data for a step or group.
+     */
+    final class Box {
+
+        private Step step;
+        private int remainingRequirements;
+
+        private int absoluteTier = 0;
+        private int tier;
+        private int depth = 1;
+        private int breadth;
+        private int center, top;
+
+        private Box(Step step, int remainingRequirements) {
+            this.step = step;
+            this.remainingRequirements = remainingRequirements + 1;
+            breadth = step == null || step instanceof Group ? 0 : 1;
+        }
+
+        private void latchTiers(int absoluteTier, int tier, Group from) {
+            this.absoluteTier = Math.max(this.absoluteTier, absoluteTier);
+            if (step == null || step.group() == from) {
+                this.tier = Math.max(this.tier, tier);
+            }
+        }
+
+        public void latchBreadth(int breadth) {
+            this.breadth = Math.max(this.breadth, breadth);
+        }
+
+        void setTierAndDepth(int absoluteTier, int tier, int depth, Group from) {
+            latchTiers(absoluteTier, tier, from);
+            this.depth = depth;
+        }
+
+        boolean visitAndLatchMaxTier(int absoluteTier, int tier, Group from) {
+            latchTiers(absoluteTier, tier, from);
+            --remainingRequirements;
+            return remainingRequirements == 0;
+        }
+
+        Step step() {
+            return step;
+        }
+
+        public int absoluteTier() {
+            return absoluteTier;
+        }
+
+        int tier() {
+            return tier;
+        }
+
+        int depth() {
+            return depth;
+        }
+
+        int breadth() {
+            return breadth;
+        }
+
+        int top() {
+            return top;
+        }
+
+        int center() {
+            return center;
+        }
+
+        public void updateCenter(int top, int center) {
+            this.top = top;
+            this.center = center;
+        }
+    }
+}