ONOS-4971: Synthetic Link Data -- WIP, merge anyway

- created temp Topology2 View (topoX) to "process" and display topology data.
- made root layout parent of itself (just like /.. = /) to simplify layout hierarchy operations.
- added nodeType property to JSON rep of regions/devices/hosts.
- augmented peers to include devices.
- added skeleton topo2NavRegion event.

Change-Id: I8219125d7dfe33d211350ae27111a3d9de6eb4ca
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java b/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
index 79f3d77..0cbc848 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
@@ -134,6 +134,7 @@
 
                 // FIXME: leave commented out for now, while still under development
 //                new UiView(NETWORK, "topo2", "New-Topo"),
+//                new UiView(NETWORK, "topoX", "Topo-X"),
 
                 new UiView(NETWORK, "device", "Devices", "nav_devs"),
                 new UiViewHidden("flow"),
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2Jsonifier.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2Jsonifier.java
index 02df84c..8a26a92 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2Jsonifier.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2Jsonifier.java
@@ -40,6 +40,8 @@
 import org.onosproject.ui.model.topo.UiNode;
 import org.onosproject.ui.model.topo.UiRegion;
 import org.onosproject.ui.model.topo.UiTopoLayout;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -62,6 +64,12 @@
     private static final String E_UNKNOWN_UI_NODE =
             "Unknown subclass of UiNode: ";
 
+    private static final String REGION = "region";
+    private static final String DEVICE = "device";
+    private static final String HOST = "host";
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
     private final ObjectMapper mapper = new ObjectMapper();
 
     private ServiceDirectory directory;
@@ -245,6 +253,7 @@
     private ObjectNode json(UiDevice device) {
         ObjectNode node = objectNode()
                 .put("id", device.idAsString())
+                .put("nodeType", DEVICE)
                 .put("type", device.type())
                 .put("online", device.isOnline())
                 .put("master", nullIsEmpty(device.master()))
@@ -266,6 +275,7 @@
     private ObjectNode json(UiHost host) {
         return objectNode()
                 .put("id", host.idAsString())
+                .put("nodeType", HOST)
                 .put("layer", host.layer());
         // TODO: complete host details
     }
@@ -281,10 +291,31 @@
     private ObjectNode jsonClosedRegion(UiRegion region) {
         return objectNode()
                 .put("id", region.idAsString())
+                .put("nodeType", REGION)
                 .put("nDevs", region.deviceCount());
         // TODO: complete closed-region details
     }
 
+    /**
+     * Returns a JSON array representation of a set of regions/devices. Note
+     * that the information is sufficient for showing regions as nodes.
+     *
+     * @param nodes the nodes
+     * @return a JSON representation of the nodes
+     */
+    public ArrayNode closedNodes(Set<UiNode> nodes) {
+        ArrayNode array = arrayNode();
+        for (UiNode node: nodes) {
+            if (node instanceof UiRegion) {
+                array.add(jsonClosedRegion((UiRegion) node));
+            } else if (node instanceof UiDevice) {
+                array.add(json((UiDevice) node));
+            } else {
+                log.warn("Unexpected node instance: {}", node.getClass());
+            }
+        }
+        return array;
+    }
 
     /**
      * Returns a JSON array representation of a list of regions. Note that the
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2ViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2ViewMessageHandler.java
index 5449d93..7795271 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2ViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2ViewMessageHandler.java
@@ -24,6 +24,7 @@
 import org.onosproject.ui.UiMessageHandler;
 import org.onosproject.ui.impl.UiWebSocket;
 import org.onosproject.ui.model.topo.UiClusterMember;
+import org.onosproject.ui.model.topo.UiNode;
 import org.onosproject.ui.model.topo.UiRegion;
 import org.onosproject.ui.model.topo.UiTopoLayout;
 import org.slf4j.Logger;
@@ -55,8 +56,9 @@
     private final Logger log = LoggerFactory.getLogger(getClass());
 
     // === Inbound event identifiers
-    private static final String TOPO2_START = "topo2Start";
-    private static final String TOPO2_STOP = "topo2Stop";
+    private static final String START = "topo2Start";
+    private static final String NAV_REGION = "topo2navRegion";
+    private static final String STOP = "topo2Stop";
 
     // === Outbound event identifiers
     private static final String ALL_INSTANCES = "topo2AllInstances";
@@ -83,6 +85,7 @@
     protected Collection<RequestHandler> createRequestHandlers() {
         return ImmutableSet.of(
                 new Topo2Start(),
+                new Topo2NavRegion(),
                 new Topo2Stop()
         );
     }
@@ -92,7 +95,7 @@
 
     private final class Topo2Start extends RequestHandler {
         private Topo2Start() {
-            super(TOPO2_START);
+            super(START);
         }
 
         @Override
@@ -124,10 +127,10 @@
             Set<UiRegion> kids = topoSession.getSubRegions(currentLayout);
             sendMessage(CURRENT_REGION, t2json.region(region, kids));
 
-            // these are the regions that are siblings to this one
-            Set<UiRegion> peers = topoSession.getPeerRegions(currentLayout);
+            // these are the regions/devices that are siblings to this region
+            Set<UiNode> peers = topoSession.getPeerNodes(currentLayout);
             ObjectNode peersPayload = objectNode();
-            peersPayload.set("peers", t2json.closedRegions(peers));
+            peersPayload.set("peers", t2json.closedNodes(peers));
             sendMessage(PEER_REGIONS, peersPayload);
 
             // finally, tell the UI that we are done : TODO review / delete??
@@ -146,9 +149,22 @@
 
     }
 
+    private final class Topo2NavRegion extends RequestHandler {
+        private Topo2NavRegion() {
+            super(NAV_REGION);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            String dir = string(payload, "dir");
+            String rid = string(payload, "rid");
+            log.debug("NavRegion: dir={}, rid={}", dir, rid);
+        }
+    }
+
     private final class Topo2Stop extends RequestHandler {
         private Topo2Stop() {
-            super(TOPO2_STOP);
+            super(STOP);
         }
 
         @Override
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/UiTopoLayoutManager.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/UiTopoLayoutManager.java
index 28e345c..fcf4c46 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/UiTopoLayoutManager.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/UiTopoLayoutManager.java
@@ -111,15 +111,17 @@
     }
 
     @Override
-    public Set<UiTopoLayout> getPeers(UiTopoLayoutId layoutId) {
+    public Set<UiTopoLayout> getPeerLayouts(UiTopoLayoutId layoutId) {
         checkNotNull(layoutId, ID_NULL);
+
         UiTopoLayout layout = layoutMap.get(layoutId);
-        if (layout == null) {
+        if (layout == null || layout.isRoot()) {
             return Collections.emptySet();
         }
 
         UiTopoLayoutId parentId = layout.parent();
         return layoutMap.values().stream()
+                // all layouts who are NOT me and who share my parent...
                 .filter(l -> !Objects.equals(l.id(), layoutId) &&
                         Objects.equals(l.parent(), parentId))
                 .collect(Collectors.toSet());
@@ -129,7 +131,7 @@
     public Set<UiTopoLayout> getChildren(UiTopoLayoutId layoutId) {
         checkNotNull(layoutId, ID_NULL);
         return layoutMap.values().stream()
-                .filter(l -> Objects.equals(l.parent(), layoutId))
+                .filter(l -> !l.isRoot() && Objects.equals(l.parent(), layoutId))
                 .collect(Collectors.toSet());
     }
 
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/UiTopoSession.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/UiTopoSession.java
index 47907d4..7127ec1 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/UiTopoSession.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/UiTopoSession.java
@@ -23,6 +23,7 @@
 import org.onosproject.ui.impl.topo.model.UiModelListener;
 import org.onosproject.ui.impl.topo.model.UiSharedTopologyModel;
 import org.onosproject.ui.model.topo.UiClusterMember;
+import org.onosproject.ui.model.topo.UiNode;
 import org.onosproject.ui.model.topo.UiRegion;
 import org.onosproject.ui.model.topo.UiTopoLayout;
 import org.slf4j.Logger;
@@ -170,17 +171,32 @@
     }
 
     /**
-     * Returns the regions that are "peers" to this region. That is, based on
-     * the layout the user is viewing, all the regions that are associated with
-     * layouts that share the same parent layout as this layout.
+     * Returns the regions/devices that are "peers" to this region. That is,
+     * based on the layout the user is viewing, all the regions/devices that
+     * are associated with layouts that share the same parent layout as this
+     * layout, AND that are linked to an element within this region.
      *
      * @param layout the layout being viewed
-     * @return all regions that are "siblings" to this layout's region
+     * @return all regions/devices that are "siblings" to this layout's region
      */
-    public Set<UiRegion> getPeerRegions(UiTopoLayout layout) {
-        Set<UiTopoLayout> peerLayouts = layoutService.getPeers(layout.id());
-        Set<UiRegion> peers = new HashSet<>();
-        peerLayouts.forEach(l -> peers.add(sharedModel.getRegion(l.regionId())));
+    public Set<UiNode> getPeerNodes(UiTopoLayout layout) {
+        Set<UiNode> peers = new HashSet<>();
+
+        // first, get the peer regions
+        Set<UiTopoLayout> peerLayouts = layoutService.getPeerLayouts(layout.id());
+        peerLayouts.forEach(l -> {
+            RegionId peerRegion = l.regionId();
+            peers.add(sharedModel.getRegion(peerRegion));
+        });
+
+        // now add the devices that reside in the parent region
+        if (!layout.isRoot()) {
+            UiTopoLayout parentLayout = layoutService.getLayout(layout.parent());
+            getRegion(parentLayout).devices().forEach(peers::add);
+        }
+
+        // TODO: Finally, filter out regions / devices that are not connected
+        //       directly to this region by an implicit link
         return peers;
     }