Avoid high-lighting wrong link

Change-Id: I4b8f8c73a048d030e4517beb474b11ce848a5574
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/ProtectedIntentMonitor.java b/web/gui/src/main/java/org/onosproject/ui/impl/ProtectedIntentMonitor.java
index 3a92428..81561c6 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/ProtectedIntentMonitor.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/ProtectedIntentMonitor.java
@@ -18,14 +18,18 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
+
+
+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.MarkerResource;
-import org.onosproject.net.NetworkResource;
-import org.onosproject.net.behaviour.protection.TransportEndpointDescription;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.PortCriterion;
+import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
 import org.onosproject.net.intent.FlowRuleIntent;
 import org.onosproject.net.intent.Intent;
 import org.onosproject.net.intent.OpticalConnectivityIntent;
@@ -45,12 +49,17 @@
 import org.slf4j.LoggerFactory;
 
 import java.util.Collection;
-import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.Timer;
 import java.util.TimerTask;
+import java.util.stream.Collectors;
 
+import static org.onosproject.net.MarkerResource.marker;
 import static org.onosproject.ui.impl.ProtectedIntentMonitor.ProtectedMode.IDLE;
 import static org.onosproject.ui.impl.ProtectedIntentMonitor.ProtectedMode.SELECTED_INTENT;
 
@@ -192,24 +201,46 @@
         if (selectedIntent != null) {
             List<Intent> installables = servicesBundle.intentService()
                     .getInstallableIntents(selectedIntent.key());
-            Set<Link> primary = new HashSet<>();
-            Set<Link> backup = new HashSet<>();
+
             if (installables != null) {
-                //There should be exactly two FlowRuleIntent and four
-                //ProtectionEndpointIntent for each ProtectedTransportIntent.
-                for (Intent installable : installables) {
-                    if (installable instanceof FlowRuleIntent) {
-                        handleFlowRuleIntent(primary, backup, (FlowRuleIntent) installable);
-                    } else if (installable instanceof ProtectionEndpointIntent) {
-                        handleProtectionEndpointIntent(primary, backup,
-                                                       (ProtectionEndpointIntent) installable);
-                    } else {
-                        log.warn("Intent {} is not an expected installable type {} " +
-                                         "related to ProtectedTransportIntent",
-                                 installable.id(), installable.getClass().getSimpleName());
-                        stopMonitoring();
-                    }
+                ProtectionEndpointIntent ep1 = installables.stream()
+                        .filter(ProtectionEndpointIntent.class::isInstance)
+                        .map(ProtectionEndpointIntent.class::cast)
+                        .findFirst().orElse(null);
+                ProtectionEndpointIntent ep2 = installables.stream()
+                        .filter(ii -> !ii.equals(ep1))
+                        .filter(ProtectionEndpointIntent.class::isInstance)
+                        .map(ProtectionEndpointIntent.class::cast)
+                        .findFirst().orElse(null);
+                if (ep1 == null || ep2 == null) {
+                    log.warn("Selected Intent {} didn't have 2 protection endpoints",
+                             selectedIntent.key());
+                    stopMonitoring();
+                    return highlights;
                 }
+                Set<Link> primary = new LinkedHashSet<>();
+                Set<Link> backup = new LinkedHashSet<>();
+
+                Map<Boolean, List<FlowRuleIntent>> transits = installables.stream()
+                    .filter(FlowRuleIntent.class::isInstance)
+                    .map(FlowRuleIntent.class::cast)
+                    // only consider fwd links so that ants march in one direction
+                    // TODO: ⇅didn't help need further investigation.
+                    //.filter(i -> !i.resources().contains(marker("rev")))
+                    .collect(Collectors.groupingBy(this::isPrimary));
+
+                // walk primary
+                ConnectPoint primHead = ep1.description().paths().get(0).output().connectPoint();
+                ConnectPoint primTail = ep2.description().paths().get(0).output().connectPoint();
+                List<FlowRuleIntent> primTransit = transits.getOrDefault(true, ImmutableList.of());
+                populateLinks(primary, primHead, primTail, primTransit);
+
+                // walk backup
+                ConnectPoint backHead = ep1.description().paths().get(1).output().connectPoint();
+                ConnectPoint backTail = ep2.description().paths().get(1).output().connectPoint();
+                List<FlowRuleIntent> backTransit = transits.getOrDefault(false, ImmutableList.of());
+                populateLinks(backup, backHead, backTail, backTransit);
+
                 boolean isOptical = selectedIntent instanceof OpticalConnectivityIntent;
                 //last parameter (traffic) signals if the link is highlited with ants or solid line
                 //Flavor is swapped so green is primary path.
@@ -223,6 +254,7 @@
                     processLinks(linkMap, backup, Flavor.SECONDARY_HIGHLIGHT,
                                  isOptical, false, PROTECTED_MOD_BACKUP_SET);
                 }
+
                 updateHighlights(highlights, primary);
                 updateHighlights(highlights, backup);
                 colorLinks(highlights, linkMap);
@@ -238,31 +270,75 @@
 
     // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
-    private void handleProtectionEndpointIntent(Set<Link> primary, Set<Link> backup,
-                                                ProtectionEndpointIntent peIntent) {
-        TransportEndpointDescription primaryDesc = peIntent
-                .description().paths().get(0);
-        TransportEndpointDescription secondaryDesc = peIntent
-                .description().paths().get(1);
-        primary.addAll(servicesBundle.linkService()
-                               .getLinks(primaryDesc.output()
-                                                 .connectPoint()));
-        backup.addAll(servicesBundle.linkService()
-                              .getLinks(secondaryDesc.output()
-                                                .connectPoint()));
+    /**
+     * Populate Links along the primary/backup path.
+     *
+     * @param links link collection to populate [output]
+     * @param head head-end of primary/backup path
+     * @param tail tail-end of primary/backup path
+     * @param transit Intents if any
+     */
+    private void populateLinks(Set<Link> links,
+                               ConnectPoint head,
+                               ConnectPoint tail,
+                               List<FlowRuleIntent> transit) {
+        // find first hop link
+        Link first = transit.stream()
+                // search for Link head -> transit Intent head
+                // as first candidate of 1st hop Link
+                .flatMap(fri -> fri.flowRules().stream())
+                .map(fr ->
+                    // find first input port from FlowRule
+                    Optional.ofNullable(fr.selector().getCriterion(Criterion.Type.IN_PORT))
+                        .filter(PortCriterion.class::isInstance)
+                        .map(PortCriterion.class::cast)
+                        .map(PortCriterion::port)
+                        .map(pn -> new ConnectPoint(fr.deviceId(), pn))
+                        .orElse(null)
+                ).filter(Objects::nonNull)
+                .map(dst -> servicesBundle.linkService().getLink(head, dst))
+                .filter(Objects::nonNull)
+                .findFirst()
+                // if there isn't one probably 1 hop to the tail
+                .orElse(servicesBundle.linkService().getLink(head, tail));
+
+        // add first link
+        if (first != null) {
+            links.add(first);
+        }
+
+        // add links in the middle if any
+        transit.forEach(fri -> links.addAll(linkResources(fri)));
+
+        // add last hop if any
+        Lists.reverse(transit).stream()
+                // search for Link transit Intent tail -> tail
+                // as candidate of last hop Link
+                .flatMap(fri -> ImmutableList.copyOf(fri.flowRules()).reverse().stream())
+                .map(fr ->
+                    // find first output port from FlowRule
+                    fr.treatment().allInstructions().stream()
+                            .filter(OutputInstruction.class::isInstance).findFirst()
+                        .map(OutputInstruction.class::cast)
+                        .map(OutputInstruction::port)
+                        .map(pn -> new ConnectPoint(fr.deviceId(), pn))
+                        .orElse(null)
+                ).filter(Objects::nonNull)
+                .map(src -> servicesBundle.linkService().getLink(src, tail))
+                .filter(Objects::nonNull)
+                .findFirst()
+                .ifPresent(links::add);
     }
 
-    private void handleFlowRuleIntent(Set<Link> primary, Set<Link> backup,
-                                      FlowRuleIntent frIntent) {
-        boolean protection1 = frIntent.resources().stream()
-                .filter(r -> r instanceof MarkerResource)
-                .map(NetworkResource::toString)
-                .anyMatch(rstring -> rstring.equals(PRIMARY_PATH_TAG));
-        if (protection1) {
-            primary.addAll(linkResources(frIntent));
-        } else {
-            backup.addAll(linkResources(frIntent));
-        }
+    /**
+     * Returns true if specified intent is marked with primary marker resource.
+     *
+     * @param intent to test
+     * @return true if it is an Intent taking part of primary transit path
+     */
+    private boolean isPrimary(Intent intent) {
+        return intent.resources()
+                .contains(marker(PRIMARY_PATH_TAG));
     }
 
     // returns true if the backup path is the one where the traffic is currently flowing