Lazily compute SCC, etc. on demand

- TODO computeCost does not correspond to actual computation cost now

Change-Id: Iffe3093c81bbb51d5feb46117fae8be092cf9288
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/topology/impl/DefaultTopology.java b/core/store/dist/src/main/java/org/onlab/onos/store/topology/impl/DefaultTopology.java
index 3ee7ae2..a2b8c13 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/topology/impl/DefaultTopology.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/topology/impl/DefaultTopology.java
@@ -15,12 +15,17 @@
  */
 package org.onlab.onos.store.topology.impl;
 
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSetMultimap;
+
 import org.onlab.graph.DijkstraGraphSearch;
 import org.onlab.graph.GraphPathSearch;
+import org.onlab.graph.GraphPathSearch.Result;
 import org.onlab.graph.TarjanGraphSearch;
+import org.onlab.graph.TarjanGraphSearch.SCCResult;
 import org.onlab.onos.net.AbstractModel;
 import org.onlab.onos.net.ConnectPoint;
 import org.onlab.onos.net.DefaultPath;
@@ -46,8 +51,6 @@
 
 import static com.google.common.base.MoreObjects.toStringHelper;
 import static com.google.common.collect.ImmutableSetMultimap.Builder;
-import static org.onlab.graph.GraphPathSearch.Result;
-import static org.onlab.graph.TarjanGraphSearch.SCCResult;
 import static org.onlab.onos.core.CoreService.CORE_PROVIDER_ID;
 import static org.onlab.onos.net.Link.State.ACTIVE;
 import static org.onlab.onos.net.Link.State.INACTIVE;
@@ -68,18 +71,15 @@
     private final long computeCost;
     private final TopologyGraph graph;
 
-    private final SCCResult<TopologyVertex, TopologyEdge> clusterResults;
-    private final ImmutableMap<DeviceId, Result<TopologyVertex, TopologyEdge>> results;
-    private final ImmutableSetMultimap<PathKey, Path> paths;
+    private final Supplier<SCCResult<TopologyVertex, TopologyEdge>> clusterResults;
+    private final Supplier<ImmutableMap<DeviceId, Result<TopologyVertex, TopologyEdge>>> results;
+    private final Supplier<ImmutableSetMultimap<PathKey, Path>> paths;
 
-    private final ImmutableMap<ClusterId, TopologyCluster> clusters;
-    private final ImmutableSet<ConnectPoint> infrastructurePoints;
-    private final ImmutableSetMultimap<ClusterId, ConnectPoint> broadcastSets;
+    private final Supplier<ImmutableMap<ClusterId, TopologyCluster>> clusters;
+    private final Supplier<ImmutableSet<ConnectPoint>> infrastructurePoints;
+    private final Supplier<ImmutableSetMultimap<ClusterId, ConnectPoint>> broadcastSets;
 
-    private ImmutableMap<DeviceId, TopologyCluster> clustersByDevice;
-    private ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster;
-    private ImmutableSetMultimap<TopologyCluster, Link> linksByCluster;
-
+    private final Supplier<ClusterIndexes> clusterIndexes;
 
     /**
      * Creates a topology descriptor attributed to the specified provider.
@@ -95,16 +95,17 @@
         this.graph = new DefaultTopologyGraph(description.vertexes(),
                                               description.edges());
 
-        this.results = searchForShortestPaths();
-        this.paths = buildPaths();
 
-        this.clusterResults = searchForClusters();
-        this.clusters = buildTopologyClusters();
+        this.results = Suppliers.memoize(() -> searchForShortestPaths());
+        this.paths = Suppliers.memoize(() -> buildPaths());
 
-        buildIndexes();
+        this.clusterResults = Suppliers.memoize(() -> searchForClusters());
+        this.clusters = Suppliers.memoize(() -> buildTopologyClusters());
 
-        this.broadcastSets = buildBroadcastSets();
-        this.infrastructurePoints = findInfrastructurePoints();
+        this.clusterIndexes = Suppliers.memoize(() -> buildIndexes());
+
+        this.broadcastSets = Suppliers.memoize(() -> buildBroadcastSets());
+        this.infrastructurePoints = Suppliers.memoize(() -> findInfrastructurePoints());
         this.computeCost = Math.max(0, System.nanoTime() - time);
     }
 
@@ -120,7 +121,7 @@
 
     @Override
     public int clusterCount() {
-        return clusters.size();
+        return clusters.get().size();
     }
 
     @Override
@@ -135,7 +136,19 @@
 
     @Override
     public int pathCount() {
-        return paths.size();
+        return paths.get().size();
+    }
+
+    private ImmutableMap<DeviceId, TopologyCluster> clustersByDevice() {
+        return clusterIndexes.get().clustersByDevice;
+    }
+
+    private ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster() {
+        return clusterIndexes.get().devicesByCluster;
+    }
+
+    private ImmutableSetMultimap<TopologyCluster, Link> linksByCluster() {
+        return clusterIndexes.get().linksByCluster;
     }
 
     /**
@@ -153,7 +166,7 @@
      * @return set of clusters
      */
     Set<TopologyCluster> getClusters() {
-        return ImmutableSet.copyOf(clusters.values());
+        return ImmutableSet.copyOf(clusters.get().values());
     }
 
     /**
@@ -163,7 +176,7 @@
      * @return topology cluster
      */
     TopologyCluster getCluster(ClusterId clusterId) {
-        return clusters.get(clusterId);
+        return clusters.get().get(clusterId);
     }
 
     /**
@@ -173,7 +186,7 @@
      * @return topology cluster
      */
     TopologyCluster getCluster(DeviceId deviceId) {
-        return clustersByDevice.get(deviceId);
+        return clustersByDevice().get(deviceId);
     }
 
     /**
@@ -183,7 +196,7 @@
      * @return cluster devices
      */
     Set<DeviceId> getClusterDevices(TopologyCluster cluster) {
-        return devicesByCluster.get(cluster);
+        return devicesByCluster().get(cluster);
     }
 
     /**
@@ -193,7 +206,7 @@
      * @return cluster links
      */
     Set<Link> getClusterLinks(TopologyCluster cluster) {
-        return linksByCluster.get(cluster);
+        return linksByCluster().get(cluster);
     }
 
     /**
@@ -203,7 +216,7 @@
      * @return true if infrastructure
      */
     boolean isInfrastructure(ConnectPoint connectPoint) {
-        return infrastructurePoints.contains(connectPoint);
+        return infrastructurePoints.get().contains(connectPoint);
     }
 
     /**
@@ -219,14 +232,14 @@
         }
 
         // Find the cluster to which the device belongs.
-        TopologyCluster cluster = clustersByDevice.get(connectPoint.deviceId());
+        TopologyCluster cluster = clustersByDevice().get(connectPoint.deviceId());
         if (cluster == null) {
             throw new IllegalArgumentException("No cluster found for device " + connectPoint.deviceId());
         }
 
         // If the broadcast set is null or empty, or if the point explicitly
         // belongs to it, return true;
-        Set<ConnectPoint> points = broadcastSets.get(cluster.id());
+        Set<ConnectPoint> points = broadcastSets.get().get(cluster.id());
         return points == null || points.isEmpty() || points.contains(connectPoint);
     }
 
@@ -237,7 +250,7 @@
      * @return size of the cluster broadcast set
      */
     int broadcastSetSize(ClusterId clusterId) {
-        return broadcastSets.get(clusterId).size();
+        return broadcastSets.get().get(clusterId).size();
     }
 
     /**
@@ -249,7 +262,7 @@
      * @return set of shortest paths
      */
     Set<Path> getPaths(DeviceId src, DeviceId dst) {
-        return paths.get(new PathKey(src, dst));
+        return paths.get().get(new PathKey(src, dst));
     }
 
     /**
@@ -295,8 +308,8 @@
     // Builds network paths from the graph path search results
     private ImmutableSetMultimap<PathKey, Path> buildPaths() {
         Builder<PathKey, Path> builder = ImmutableSetMultimap.builder();
-        for (DeviceId deviceId : results.keySet()) {
-            Result<TopologyVertex, TopologyEdge> result = results.get(deviceId);
+        for (DeviceId deviceId : results.get().keySet()) {
+            Result<TopologyVertex, TopologyEdge> result = results.get().get(deviceId);
             for (org.onlab.graph.Path<TopologyVertex, TopologyEdge> path : result.paths()) {
                 builder.put(new PathKey(path.src().deviceId(), path.dst().deviceId()),
                             networkPath(path));
@@ -363,7 +376,7 @@
     // Processes a map of broadcast sets for each cluster.
     private ImmutableSetMultimap<ClusterId, ConnectPoint> buildBroadcastSets() {
         Builder<ClusterId, ConnectPoint> builder = ImmutableSetMultimap.builder();
-        for (TopologyCluster cluster : clusters.values()) {
+        for (TopologyCluster cluster : clusters.get().values()) {
             addClusterBroadcastSet(cluster, builder);
         }
         return builder.build();
@@ -375,12 +388,12 @@
     private void addClusterBroadcastSet(TopologyCluster cluster,
                                         Builder<ClusterId, ConnectPoint> builder) {
         // Use the graph root search results to build the broadcast set.
-        Result<TopologyVertex, TopologyEdge> result = results.get(cluster.root());
+        Result<TopologyVertex, TopologyEdge> result = results.get().get(cluster.root());
         for (Map.Entry<TopologyVertex, Set<TopologyEdge>> entry : result.parents().entrySet()) {
             TopologyVertex vertex = entry.getKey();
 
             // Ignore any parents that lead outside the cluster.
-            if (clustersByDevice.get(vertex.deviceId()) != cluster) {
+            if (clustersByDevice().get(vertex.deviceId()) != cluster) {
                 continue;
             }
 
@@ -409,32 +422,32 @@
     }
 
     // Builds cluster-devices, cluster-links and device-cluster indexes.
-    private void buildIndexes() {
+    private ClusterIndexes buildIndexes() {
         // Prepare the index builders
         ImmutableMap.Builder<DeviceId, TopologyCluster> clusterBuilder = ImmutableMap.builder();
         ImmutableSetMultimap.Builder<TopologyCluster, DeviceId> devicesBuilder = ImmutableSetMultimap.builder();
         ImmutableSetMultimap.Builder<TopologyCluster, Link> linksBuilder = ImmutableSetMultimap.builder();
 
         // Now scan through all the clusters
-        for (TopologyCluster cluster : clusters.values()) {
+        for (TopologyCluster cluster : clusters.get().values()) {
             int i = cluster.id().index();
 
             // Scan through all the cluster vertexes.
-            for (TopologyVertex vertex : clusterResults.clusterVertexes().get(i)) {
+            for (TopologyVertex vertex : clusterResults.get().clusterVertexes().get(i)) {
                 devicesBuilder.put(cluster, vertex.deviceId());
                 clusterBuilder.put(vertex.deviceId(), cluster);
             }
 
             // Scan through all the cluster edges.
-            for (TopologyEdge edge : clusterResults.clusterEdges().get(i)) {
+            for (TopologyEdge edge : clusterResults.get().clusterEdges().get(i)) {
                 linksBuilder.put(cluster, edge.link());
             }
         }
 
         // Finalize all indexes.
-        clustersByDevice = clusterBuilder.build();
-        devicesByCluster = devicesBuilder.build();
-        linksByCluster = linksBuilder.build();
+        return new ClusterIndexes(clusterBuilder.build(),
+                                  devicesBuilder.build(),
+                                  linksBuilder.build());
     }
 
     // Link weight for measuring link cost as hop count with indirect links
@@ -463,6 +476,20 @@
         }
     }
 
+    static final class ClusterIndexes {
+        final ImmutableMap<DeviceId, TopologyCluster> clustersByDevice;
+        final ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster;
+        final ImmutableSetMultimap<TopologyCluster, Link> linksByCluster;
+
+        public ClusterIndexes(ImmutableMap<DeviceId, TopologyCluster> clustersByDevice,
+                              ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster,
+                              ImmutableSetMultimap<TopologyCluster, Link> linksByCluster) {
+            this.clustersByDevice = clustersByDevice;
+            this.devicesByCluster = devicesByCluster;
+            this.linksByCluster = linksByCluster;
+        }
+    }
+
     @Override
     public String toString() {
         return toStringHelper(this)