Implement host-device synthetic links.

Change-Id: I95ac1b8afd48ca551905a5448347a5fba03ab836
diff --git a/core/api/src/main/java/org/onosproject/ui/model/topo/UiEdgeLink.java b/core/api/src/main/java/org/onosproject/ui/model/topo/UiEdgeLink.java
index f52fbcb..e2dc7fd 100644
--- a/core/api/src/main/java/org/onosproject/ui/model/topo/UiEdgeLink.java
+++ b/core/api/src/main/java/org/onosproject/ui/model/topo/UiEdgeLink.java
@@ -16,22 +16,14 @@
 
 package org.onosproject.ui.model.topo;
 
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.EdgeLink;
-import org.onosproject.net.PortNumber;
-
 /**
  * Designates a link between a device and a host; that is, an edge link.
  */
 public class UiEdgeLink extends UiLink {
 
-    private static final String E_UNASSOC =
-            "backing link not associated with this UI edge link: ";
-
-    // private (synthetic) host link
-    private DeviceId edgeDevice;
-    private PortNumber edgePort;
-    private EdgeLink edgeLink;
+    private final String hostId;
+    private final String edgeDevice;
+    private final String edgePort;
 
     /**
      * Creates a UI link.
@@ -41,51 +33,25 @@
      */
     public UiEdgeLink(UiTopology topology, UiLinkId id) {
         super(topology, id);
+        hostId = id.idA();
+        edgeDevice = id.elementB().toString();
+        edgePort = id.portB().toString();
     }
 
     @Override
     public String endPointA() {
-        return edgeLink.hostId().toString();
+        return hostId;
     }
 
     @Override
     public String endPointB() {
-        return edgeDevice + UiLinkId.ID_PORT_DELIMITER + edgePort;
+        return edgeDevice;
     }
 
     // no port for end-point A
 
     @Override
     public String endPortB() {
-        return edgePort.toString();
+        return edgePort;
     }
-
-    @Override
-    protected void destroy() {
-        edgeDevice = null;
-        edgePort = null;
-        edgeLink = null;
-    }
-
-    /**
-     * Attaches the given edge link to this UI link. This method will
-     * throw an exception if this UI link is not representative of the
-     * supplied link.
-     *
-     * @param elink edge link to attach
-     * @throws IllegalArgumentException if the link is not appropriate
-     */
-    public void attachEdgeLink(EdgeLink elink) {
-        UiLinkId.Direction d = id.directionOf(elink);
-        // Expected direction of edge links is A-to-B (Host to device)
-        // but checking not null is a sufficient test
-        if (d == null) {
-            throw new IllegalArgumentException(E_UNASSOC + elink);
-        }
-
-        edgeLink = elink;
-        edgeDevice = elink.hostLocation().deviceId();
-        edgePort = elink.hostLocation().port();
-    }
-
 }
diff --git a/core/api/src/main/java/org/onosproject/ui/model/topo/UiLinkId.java b/core/api/src/main/java/org/onosproject/ui/model/topo/UiLinkId.java
index ca85fb2..c8dae05 100644
--- a/core/api/src/main/java/org/onosproject/ui/model/topo/UiLinkId.java
+++ b/core/api/src/main/java/org/onosproject/ui/model/topo/UiLinkId.java
@@ -19,6 +19,7 @@
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.ElementId;
+import org.onosproject.net.HostId;
 import org.onosproject.net.Link;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.region.RegionId;
@@ -49,7 +50,7 @@
         B_TO_A
     }
 
-    private static final String CP_DELIMITER = "~";
+    static final String CP_DELIMITER = "~";
     static final String ID_PORT_DELIMITER = "/";
 
     private final RegionId regionA;
@@ -61,6 +62,8 @@
     private final PortNumber portB;
 
     private final String idStr;
+    private final String idA;
+    private final String idB;
 
     /**
      * Creates a UI link identifier. It is expected that A comes before B when
@@ -82,8 +85,10 @@
         regionA = null;
         regionB = null;
 
-        idStr = a + ID_PORT_DELIMITER + pa + CP_DELIMITER +
-                b + ID_PORT_DELIMITER + pb;
+        // NOTE: for edgelinks, hosts are always element A
+        idA = (a instanceof HostId) ? a.toString() : a + ID_PORT_DELIMITER + pa;
+        idB = b + ID_PORT_DELIMITER + pb;
+        idStr = idA + CP_DELIMITER + idB;
     }
 
     /**
@@ -102,7 +107,9 @@
         portA = null;
         portB = null;
 
-        idStr = a + CP_DELIMITER + b;
+        idA = a.toString();
+        idB = b.toString();
+        idStr = idA + CP_DELIMITER + idB;
     }
 
     /**
@@ -122,7 +129,9 @@
         elementA = null;
         portA = null;
 
-        idStr = r + CP_DELIMITER + elementB + ID_PORT_DELIMITER + portB;
+        idA = r.toString();
+        idB = elementB + ID_PORT_DELIMITER + portB;
+        idStr = idA + CP_DELIMITER + idB;
     }
 
     @Override
@@ -131,6 +140,24 @@
     }
 
     /**
+     * String representation of endpoint A.
+     *
+     * @return string rep of endpoint A
+     */
+    public String idA() {
+        return idA;
+    }
+
+    /**
+     * String representation of endpoint B.
+     *
+     * @return string rep of endpoint B
+     */
+    public String idB() {
+        return idB;
+    }
+
+    /**
      * Returns the identifier of the first element. Note that the returned
      * value will be null if this identifier is for a region-region link.
      *
diff --git a/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopology.java b/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopology.java
index f4ffb02..744c996 100644
--- a/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopology.java
+++ b/core/api/src/main/java/org/onosproject/ui/model/topo/UiTopology.java
@@ -490,12 +490,24 @@
             log.debug("Synthetic link: {}", synthetic);
         });
 
+        slinks.addAll(wrapHostLinks(nullRegion()));
+        for (UiRegion r: allRegions()) {
+            slinks.addAll(wrapHostLinks(r));
+        }
 
         synthLinks.clear();
         synthLinks.addAll(slinks);
+    }
 
-        // TODO : compute and add host-device links to synthLinks...
+    private Set<UiSynthLink> wrapHostLinks(UiRegion region) {
+        RegionId regionId = region.id();
+        return region.hosts().stream().map(h -> wrapHostLink(regionId, h))
+                .collect(Collectors.toSet());
+    }
 
+    private UiSynthLink wrapHostLink(RegionId regionId, UiHost host) {
+        UiEdgeLink elink = new UiEdgeLink(this, host.edgeLinkId());
+        return new UiSynthLink(regionId, elink);
     }
 
     private UiSynthLink inferSyntheticLink(UiDeviceLink link) {
diff --git a/core/api/src/test/java/org/onosproject/ui/model/topo/UiEdgeLinkTest.java b/core/api/src/test/java/org/onosproject/ui/model/topo/UiEdgeLinkTest.java
index d5b213a..97a5480 100644
--- a/core/api/src/test/java/org/onosproject/ui/model/topo/UiEdgeLinkTest.java
+++ b/core/api/src/test/java/org/onosproject/ui/model/topo/UiEdgeLinkTest.java
@@ -32,7 +32,8 @@
 public class UiEdgeLinkTest extends AbstractUiModelTest {
 
     private static final String PHANTOM_HOST_ID = "00:00:00:00:00:00/None";
-    private static final String D1_P8 = "dev-1/8";
+    private static final String S_D1 = "dev-1";
+    private static final String S_P8 = "8";
 
     private static final DeviceId DEV = DeviceId.deviceId("dev-1");
     private static final PortNumber P8 = PortNumber.portNumber(8);
@@ -46,12 +47,16 @@
         title("basic");
         UiLinkId id = UiLinkId.uiLinkId(EDGE_LINK);
         UiEdgeLink link = new UiEdgeLink(null, id);
-        link.attachEdgeLink(EDGE_LINK);
         print(link);
         print(link.endPointA());
+        print(link.endPortA());
         print(link.endPointB());
+        print(link.endPortB());
 
         assertEquals("bad end point A", PHANTOM_HOST_ID, link.endPointA());
-        assertEquals("bad end point B", D1_P8, link.endPointB());
+        assertEquals("bad end port A", null, link.endPortA());
+
+        assertEquals("bad end point B", S_D1, link.endPointB());
+        assertEquals("bad end port B", S_P8, link.endPortB());
     }
 }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/model/ModelCache.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/model/ModelCache.java
index 69c8c8f..8259fb3 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/model/ModelCache.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/model/ModelCache.java
@@ -431,27 +431,26 @@
         host.setEdgeLinkId(elinkId);
 
         // add synthesized edge link to the topology
-        UiEdgeLink edgeLink = addNewEdgeLink(elinkId);
-        edgeLink.attachEdgeLink(elink);
+        addNewEdgeLink(elinkId);
 
         return host;
     }
 
-    private void insertNewUiEdgeLink(UiLinkId id, EdgeLink e) {
-        UiEdgeLink newEdgeLink = addNewEdgeLink(id);
-        newEdgeLink.attachEdgeLink(e);
+    private void insertNewUiEdgeLink(UiLinkId id) {
+        addNewEdgeLink(id);
     }
 
     private void updateHost(UiHost uiHost, Host h) {
         UiEdgeLink existing = uiTopology.findEdgeLink(uiHost.edgeLinkId());
 
+        // TODO: review - do we need EdgeLink now that we are creating from id only?
         EdgeLink currentElink = synthesizeLink(h);
         UiLinkId currentElinkId = uiLinkId(currentElink);
 
         if (existing != null) {
             if (!currentElinkId.equals(existing.id())) {
                 // edge link has changed
-                insertNewUiEdgeLink(currentElinkId, currentElink);
+                insertNewUiEdgeLink(currentElinkId);
                 uiHost.setEdgeLinkId(currentElinkId);
 
                 uiTopology.remove(existing);
@@ -459,7 +458,7 @@
 
         } else {
             // no previously existing edge link
-            insertNewUiEdgeLink(currentElinkId, currentElink);
+            insertNewUiEdgeLink(currentElinkId);
             uiHost.setEdgeLinkId(currentElinkId);
 
         }