Change calcPathIntent method to accept IntentOperationList

PathCalcRuntime.calcPathIntent()
 - accepts app-level IntentOperationList
 - returns PathIntents' IntentOperationList

PathCalcRuntimeModule class
 - retains app-level intents and path-level intents
 - publishes PathIntents' IntentOperationList

Change-Id: I70576390437f36d8b88849c0098966994b29900d
diff --git a/src/main/java/net/onrc/onos/intent/IntentOperationList.java b/src/main/java/net/onrc/onos/intent/IntentOperationList.java
index 1c916db..af00373 100644
--- a/src/main/java/net/onrc/onos/intent/IntentOperationList.java
+++ b/src/main/java/net/onrc/onos/intent/IntentOperationList.java
@@ -4,4 +4,8 @@
 
 public class IntentOperationList extends ArrayList<IntentOperation> {
 	private static final long serialVersionUID = -3894081461861052610L;
+	
+	public boolean add(IntentOperation.Operator op, Intent intent) {
+		return add(new IntentOperation(op, intent));
+	}
 }
diff --git a/src/main/java/net/onrc/onos/intent/runtime/IPathCalcRuntimeService.java b/src/main/java/net/onrc/onos/intent/runtime/IPathCalcRuntimeService.java
new file mode 100644
index 0000000..9e53732
--- /dev/null
+++ b/src/main/java/net/onrc/onos/intent/runtime/IPathCalcRuntimeService.java
@@ -0,0 +1,12 @@
+package net.onrc.onos.intent.runtime;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.onrc.onos.intent.IntentMap;
+import net.onrc.onos.intent.IntentOperationList;
+
+public interface IPathCalcRuntimeService extends IFloodlightService {
+	public IntentOperationList executeIntentOperations(IntentOperationList list);
+	public IntentMap getHighLevelIntents();
+	public IntentMap getPathIntents();
+	public void purgeIntents();
+}
diff --git a/src/main/java/net/onrc/onos/intent/runtime/PathCalcRuntime.java b/src/main/java/net/onrc/onos/intent/runtime/PathCalcRuntime.java
index d22d516..32fe746 100644
--- a/src/main/java/net/onrc/onos/intent/runtime/PathCalcRuntime.java
+++ b/src/main/java/net/onrc/onos/intent/runtime/PathCalcRuntime.java
@@ -1,11 +1,10 @@
 package net.onrc.onos.intent.runtime;
 
-import java.util.Collection;
+import java.util.HashMap;
 
 import net.floodlightcontroller.core.module.IFloodlightService;
 import net.onrc.onos.intent.ConstrainedBFSTree;
 import net.onrc.onos.intent.ConstrainedShortestPathIntent;
-import net.onrc.onos.intent.Intent;
 import net.onrc.onos.intent.IntentOperation;
 import net.onrc.onos.intent.IntentOperationList;
 import net.onrc.onos.intent.PathIntent;
@@ -24,45 +23,61 @@
 		this.graph = g;
 	}
 
-	public PathIntentMap calcPathIntents(Collection<Intent> highLevelIntents, PathIntentMap pathIntents) {
-		IntentOperationList intentOpList = new IntentOperationList();
+	/**
+	 * calculate shortest-path and constrained-shortest-path intents into low-level path intents
+	 * @param intentOpList IntentOperationList having instances of ShortestPathIntent/ConstrainedShortestPathIntent
+	 * @param pathIntents a set of current low-level intents
+	 * @return IntentOperationList for PathIntent instances
+	 */
+	public IntentOperationList calcPathIntents(IntentOperationList intentOpList, PathIntentMap pathIntents) {
+		IntentOperationList pathIntentOpList = new IntentOperationList();
+		HashMap<Switch, ConstrainedBFSTree> spfTrees = new HashMap<>();
 
-		for (Intent intent: highLevelIntents) {
-			if (!(intent instanceof ShortestPathIntent)) {
-				// unsupported intent type.
-				// TODO should push back the intent to caller
-				continue;
-			}
+		for (IntentOperation intentOp: intentOpList) {
+			switch (intentOp.operator) {
+			case ADD:
+				if (!(intentOp.intent instanceof ShortestPathIntent)) {
+					// unsupported intent type.
+					// TODO should push back the intent to caller
+					continue;
+				}
 
-			ShortestPathIntent spIntent = (ShortestPathIntent) intent;
-			Switch srcSwitch = graph.getSwitch(spIntent.getSrcSwitchDpid());
-			Switch dstSwitch = graph.getSwitch(spIntent.getDstSwitchDpid());
-			if (srcSwitch == null || dstSwitch == null) {
-				// incomplete intent.
-				// TODO should push back the intent to caller
-				continue;
-			}
+				ShortestPathIntent spIntent = (ShortestPathIntent) intentOp.intent;
+				Switch srcSwitch = graph.getSwitch(spIntent.getSrcSwitchDpid());
+				Switch dstSwitch = graph.getSwitch(spIntent.getDstSwitchDpid());
+				if (srcSwitch == null || dstSwitch == null) {
+					// incomplete intent.
+					// TODO should push back the intent to caller
+					continue;
+				}
 
-			double bandwidth = 0.0;
-			ConstrainedBFSTree tree = null;
-			if (intent instanceof ConstrainedShortestPathIntent) {
-				bandwidth = ((ConstrainedShortestPathIntent) intent).getBandwidth();
-				tree = new ConstrainedBFSTree(srcSwitch, pathIntents, bandwidth);
+				double bandwidth = 0.0;
+				ConstrainedBFSTree tree = null;
+				if (intentOp.intent instanceof ConstrainedShortestPathIntent) {
+					bandwidth = ((ConstrainedShortestPathIntent) intentOp.intent).getBandwidth();
+					tree = new ConstrainedBFSTree(srcSwitch, pathIntents, bandwidth);
+				}
+				else {
+					tree = spfTrees.get(srcSwitch);
+					if (tree == null) {
+						tree = new ConstrainedBFSTree(srcSwitch);
+						spfTrees.put(srcSwitch, tree);
+					}
+				}
+				Path path = tree.getPath(dstSwitch);
+				if (path == null) {
+					// path not found.
+					// TODO should push back the intent to caller
+					continue;
+				}
+				PathIntent pathIntent = new PathIntent("pi" + intentOp.intent.getId(), path, bandwidth, intentOp.intent);
+				pathIntentOpList.add(new IntentOperation(IntentOperation.Operator.ADD, pathIntent));
+				break;
+			case REMOVE:
+				pathIntentOpList.add(intentOp);
+				break;
 			}
-			else {
-				tree = new ConstrainedBFSTree(srcSwitch);
-			}
-			Path path = tree.getPath(dstSwitch);
-			if (path == null) {
-				// path not found.
-				// TODO should push back the intent to caller
-				continue;
-			}
-
-			PathIntent pathIntent = new PathIntent("pi" + intent.getId(), path, bandwidth, intent);
-			pathIntents.addIntent(pathIntent);
-			intentOpList.add(new IntentOperation(IntentOperation.Operator.ADD, pathIntent));
 		}
-		return pathIntents;
+		return pathIntentOpList;
 	}
 }
\ No newline at end of file
diff --git a/src/main/java/net/onrc/onos/intent/runtime/PathCalcRuntimeModule.java b/src/main/java/net/onrc/onos/intent/runtime/PathCalcRuntimeModule.java
index c32b4a7..56758cb 100644
--- a/src/main/java/net/onrc/onos/intent/runtime/PathCalcRuntimeModule.java
+++ b/src/main/java/net/onrc/onos/intent/runtime/PathCalcRuntimeModule.java
@@ -14,19 +14,20 @@
 import net.onrc.onos.intent.IntentMap;
 import net.onrc.onos.intent.IntentOperationList;
 import net.onrc.onos.intent.PathIntentMap;
+import net.onrc.onos.ofcontroller.networkgraph.INetworkGraphService;
 import net.onrc.onos.ofcontroller.networkgraph.NetworkGraph;
 
-public class PathCalcRuntimeModule implements IFloodlightModule {
+public class PathCalcRuntimeModule implements IFloodlightModule, IPathCalcRuntimeService {
 	private PathCalcRuntime runtime;
 	private IDatagridService datagridService;
-	private NetworkGraph networkGraph;
+	private INetworkGraphService networkGraphService;
 	private IntentMap highLevelIntents;
-	private PathIntentMap lowLevelIntents;
-	
+	private PathIntentMap pathIntents;
+
 	private IEventChannel<byte[], IntentOperationList> eventChannel;
 	private static final String EVENT_CHANNEL_NAME = "onos.pathintent";
 
-	
+
 	@Override
 	public Collection<Class<? extends IFloodlightService>> getModuleServices() {
 		Collection<Class<? extends IFloodlightService>> l = new ArrayList<>(1);
@@ -37,7 +38,7 @@
 	@Override
 	public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() {
 		Map<Class<? extends IFloodlightService>, IFloodlightService> m = new HashMap<>(1);
-		m.put(PathCalcRuntime.class, runtime);
+		m.put(IPathCalcRuntimeService.class, this);
 		return m;
 	}
 
@@ -45,16 +46,17 @@
 	public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
 		Collection<Class<? extends IFloodlightService>> l = new ArrayList<>();
 		l.add(IDatagridService.class);
+		l.add(INetworkGraphService.class);
 		return l;
 	}
 
 	@Override
 	public void init(FloodlightModuleContext context) throws FloodlightModuleException {
 		datagridService = context.getServiceImpl(IDatagridService.class);
-		//networkGraph = new MockNetworkGraph(); // TODO give pointer to the correct NetworkGraph
-		runtime = new PathCalcRuntime(networkGraph);
+		networkGraphService = context.getServiceImpl(INetworkGraphService.class); 
+		runtime = new PathCalcRuntime(networkGraphService.getNetworkGraph());
 		highLevelIntents = new IntentMap();
-		lowLevelIntents = new PathIntentMap(networkGraph);
+		pathIntents = new PathIntentMap(networkGraphService.getNetworkGraph());
 	}
 
 	@Override
@@ -64,25 +66,33 @@
 				byte[].class,
 				IntentOperationList.class);
 	}
-	
-	public void executeIntentOperations(IntentOperationList list) {
-		highLevelIntents.executeOperations(list);
-		lowLevelIntents = runtime.calcPathIntents(
-				highLevelIntents.getAllIntents(),
-				new PathIntentMap(networkGraph));
-		// TODO publishPathIntentOperationList(IntentOperationList list)
-	}
-	
+
 	protected void publishPathIntentOperationList(IntentOperationList list) {
 		eventChannel.addEntry(new byte[1], list); // TODO make key bytes		
 	}
-	
-	public IntentMap getIntents() {
+
+	@Override
+	public IntentOperationList executeIntentOperations(IntentOperationList list) {
+		highLevelIntents.executeOperations(list);
+		IntentOperationList pathIntentOperations = runtime.calcPathIntents(list, pathIntents);
+		pathIntents.executeOperations(pathIntentOperations);
+		publishPathIntentOperationList(pathIntentOperations);
+		return pathIntentOperations;
+	}
+
+	@Override
+	public IntentMap getHighLevelIntents() {
 		return highLevelIntents;
 	}
-	
+
+	@Override
+	public IntentMap getPathIntents() {
+		return pathIntents;
+	}
+
+	@Override
 	public void purgeIntents() {
 		highLevelIntents.purge();
-		lowLevelIntents.purge();
+		pathIntents.purge();
 	}
 }
diff --git a/src/test/java/net/onrc/onos/intent/IntentMapTest.java b/src/test/java/net/onrc/onos/intent/IntentMapTest.java
index ae44e7b..bad546d 100644
--- a/src/test/java/net/onrc/onos/intent/IntentMapTest.java
+++ b/src/test/java/net/onrc/onos/intent/IntentMapTest.java
@@ -1,9 +1,6 @@
 package net.onrc.onos.intent;
 
-import static org.junit.Assert.*;
-
-import java.util.LinkedList;
-
+import static org.junit.Assert.assertEquals;
 import net.onrc.onos.intent.Intent.IntentState;
 
 import org.junit.After;
diff --git a/src/test/java/net/onrc/onos/intent/runtime/UseCaseTest.java b/src/test/java/net/onrc/onos/intent/runtime/UseCaseTest.java
index e32da32..c12a718 100644
--- a/src/test/java/net/onrc/onos/intent/runtime/UseCaseTest.java
+++ b/src/test/java/net/onrc/onos/intent/runtime/UseCaseTest.java
@@ -1,15 +1,22 @@
 package net.onrc.onos.intent.runtime;
 
-import java.util.LinkedList;
-
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.onrc.onos.datagrid.IDatagridService;
+import net.onrc.onos.datagrid.IEventChannel;
 import net.onrc.onos.intent.ConstrainedShortestPathIntent;
 import net.onrc.onos.intent.Intent;
+import net.onrc.onos.intent.IntentOperation.Operator;
+import net.onrc.onos.intent.IntentOperationList;
 import net.onrc.onos.intent.MockNetworkGraph;
 import net.onrc.onos.intent.PathIntent;
 import net.onrc.onos.intent.PathIntentMap;
 import net.onrc.onos.intent.ShortestPathIntent;
-import net.onrc.onos.ofcontroller.networkgraph.*;
+import net.onrc.onos.ofcontroller.networkgraph.INetworkGraphService;
+import net.onrc.onos.ofcontroller.networkgraph.Link;
+import net.onrc.onos.ofcontroller.networkgraph.NetworkGraph;
 
+import org.easymock.EasyMock;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -18,17 +25,46 @@
  * @author Toshio Koide (t-koide@onlab.us)
  */
 public class UseCaseTest {
-	NetworkGraph g;
+	private NetworkGraph g;
+	private FloodlightModuleContext modContext;
+	private IDatagridService datagridService;
+	private INetworkGraphService networkGraphService;
+	@SuppressWarnings("rawtypes")
+	private IEventChannel eventChannel;
 
+	@SuppressWarnings("unchecked")
 	@Before
 	public void setUp() {
 		MockNetworkGraph graph = new MockNetworkGraph();
 		graph.createSampleTopology();
 		g = graph;
+
+		datagridService = EasyMock.createMock(IDatagridService.class);
+		networkGraphService = EasyMock.createMock(INetworkGraphService.class);
+		modContext = EasyMock.createMock(FloodlightModuleContext.class);
+		eventChannel = EasyMock.createMock(IEventChannel.class);
+
+		EasyMock.expect(modContext.getServiceImpl(EasyMock.eq(IDatagridService.class)))
+		.andReturn(datagridService).once();
+		EasyMock.expect(modContext.getServiceImpl(EasyMock.eq(INetworkGraphService.class)))
+		.andReturn(networkGraphService).once();
+		
+		networkGraphService.getNetworkGraph();
+		EasyMock.expectLastCall().andReturn(g).anyTimes();
+		
+		EasyMock.expect(datagridService.createChannel("onos.pathintent", byte[].class, IntentOperationList.class))
+		.andReturn(eventChannel).once();
+
+		EasyMock.replay(datagridService);
+		EasyMock.replay(networkGraphService);
+		EasyMock.replay(modContext);
 	}
 
 	@After
 	public void tearDown() {
+		EasyMock.verify(datagridService);
+		EasyMock.verify(networkGraphService);
+		EasyMock.verify(modContext);
 	}
 
 	private void showResult(PathIntentMap intents) {
@@ -47,69 +83,75 @@
 	}
 
 	@Test
-	public void useCase1() {
+	public void useCase1() throws FloodlightModuleException {
 		// create shortest path intents
-		LinkedList<Intent> intents = new LinkedList<Intent>();
-		intents.add(new ShortestPathIntent("1", 1L, 20L, 1L, 4L, 20L, 4L));
-		intents.add(new ShortestPathIntent("2", 2L, 20L, 2L, 6L, 20L, 5L));
-		intents.add(new ShortestPathIntent("3", 4L, 20L, 3L, 8L, 20L, 6L));
+		IntentOperationList opList = new IntentOperationList();
+		opList.add(Operator.ADD, new ShortestPathIntent("1", 1L, 20L, 1L, 4L, 20L, 4L));
+		opList.add(Operator.ADD, new ShortestPathIntent("2", 2L, 20L, 2L, 6L, 20L, 5L));
+		opList.add(Operator.ADD, new ShortestPathIntent("3", 4L, 20L, 3L, 8L, 20L, 6L));
 
-		// compile high-level intents into low-level intents (calculate paths)
-		PathCalcRuntime runtime1 = new PathCalcRuntime(g);
-		PathIntentMap pathIntents = runtime1.calcPathIntents(intents, new PathIntentMap(g));
-		
+		// compile high-level intent operations into low-level intent operations (calculate paths)
+		PathCalcRuntimeModule runtime1 = new PathCalcRuntimeModule();
+		runtime1.init(modContext);
+		runtime1.startUp(modContext);
+		IntentOperationList pathIntentOpList = runtime1.executeIntentOperations(opList);
+
 		// compile low-level intents into flow entry installation plan
 		PlanCalcRuntime runtime2 = new PlanCalcRuntime(g);
-		runtime2.addIntents(pathIntents);
+		runtime2.addIntents((PathIntentMap) runtime1.getPathIntents()); // TODO use pathIntentOpList
 
 		// show results
-		showResult(pathIntents);
+		showResult((PathIntentMap) runtime1.getPathIntents());
 		System.out.println(runtime2.getPlan());
 	}
 
 	@Test
-	public void useCase2() {
+	public void useCase2() throws FloodlightModuleException {
 		// create constrained shortest path intents
-		LinkedList<Intent> intents = new LinkedList<Intent>();
-		intents.add(new ConstrainedShortestPathIntent("1", 1L, 20L, 1L, 4L, 20L, 17L, 400.0));
-		intents.add(new ConstrainedShortestPathIntent("2", 2L, 20L, 2L, 6L, 20L, 18L, 400.0));
-		intents.add(new ConstrainedShortestPathIntent("3", 4L, 20L, 3L, 8L, 20L, 19L, 400.0));
-		intents.add(new ConstrainedShortestPathIntent("4", 3L, 20L, 4L, 8L, 20L, 20L, 400.0));
-		intents.add(new ConstrainedShortestPathIntent("5", 4L, 20L, 5L, 8L, 20L, 21L, 400.0));
+		IntentOperationList opList = new IntentOperationList();
+		opList.add(Operator.ADD, new ConstrainedShortestPathIntent("1", 1L, 20L, 1L, 4L, 20L, 17L, 400.0));
+		opList.add(Operator.ADD, new ConstrainedShortestPathIntent("2", 2L, 20L, 2L, 6L, 20L, 18L, 400.0));
+		opList.add(Operator.ADD, new ConstrainedShortestPathIntent("3", 4L, 20L, 3L, 8L, 20L, 19L, 400.0));
+		opList.add(Operator.ADD, new ConstrainedShortestPathIntent("4", 3L, 20L, 4L, 8L, 20L, 20L, 400.0));
+		opList.add(Operator.ADD, new ConstrainedShortestPathIntent("5", 4L, 20L, 5L, 8L, 20L, 21L, 400.0));
 
-		// compile high-level intents into low-level intents (calculate paths)
-		PathCalcRuntime runtime1 = new PathCalcRuntime(g);
-		PathIntentMap pathIntents = runtime1.calcPathIntents(intents, new PathIntentMap(g));
+		// compile high-level intent operations into low-level intent operations (calculate paths)
+		PathCalcRuntimeModule runtime1 = new PathCalcRuntimeModule();
+		runtime1.init(modContext);
+		runtime1.startUp(modContext);
+		IntentOperationList pathIntentOpList = runtime1.executeIntentOperations(opList);
 
 		// compile low-level intents into flow entry installation plan
 		PlanCalcRuntime runtime2 = new PlanCalcRuntime(g);
-		runtime2.addIntents(pathIntents);
+		runtime2.addIntents((PathIntentMap) runtime1.getPathIntents()); // TODO use pathIntentOpList
 
 		// show results
-		showResult(pathIntents);
+		showResult((PathIntentMap) runtime1.getPathIntents());
 		System.out.println(runtime2.getPlan());
 	}
 
 	@Test
-	public void useCase3() {
+	public void useCase3() throws FloodlightModuleException {
 		// create constrained & not best effort shortest path intents
-		LinkedList<Intent> intents = new LinkedList<Intent>();
-		intents.add(new ConstrainedShortestPathIntent("1", 1L, 20L, 1L, 4L, 20L, 6L, 600.0));
-		intents.add(new ConstrainedShortestPathIntent("2", 2L, 20L, 2L, 6L, 20L, 7L, 600.0));
-		intents.add(new ShortestPathIntent("3", 4L, 20L, 3L, 8L, 20L, 8L));
-		intents.add(new ShortestPathIntent("4", 4L, 20L, 4L, 8L, 20L, 9L));
-		intents.add(new ConstrainedShortestPathIntent("5", 4L, 20L, 5L, 8L, 20L, 10L, 600.0));
+		IntentOperationList opList = new IntentOperationList();
+		opList.add(Operator.ADD, new ConstrainedShortestPathIntent("1", 1L, 20L, 1L, 4L, 20L, 6L, 600.0));
+		opList.add(Operator.ADD, new ConstrainedShortestPathIntent("2", 2L, 20L, 2L, 6L, 20L, 7L, 600.0));
+		opList.add(Operator.ADD, new ShortestPathIntent("3", 4L, 20L, 3L, 8L, 20L, 8L));
+		opList.add(Operator.ADD, new ShortestPathIntent("4", 4L, 20L, 4L, 8L, 20L, 9L));
+		opList.add(Operator.ADD, new ConstrainedShortestPathIntent("5", 4L, 20L, 5L, 8L, 20L, 10L, 600.0));
 
-		// compile high-level intents into low-level intents (calculate paths)
-		PathCalcRuntime runtime1 = new PathCalcRuntime(g);
-		PathIntentMap pathIntents = runtime1.calcPathIntents(intents, new PathIntentMap(g));
+		// compile high-level intent operations into low-level intent operations (calculate paths)
+		PathCalcRuntimeModule runtime1 = new PathCalcRuntimeModule();
+		runtime1.init(modContext);
+		runtime1.startUp(modContext);
+		IntentOperationList pathIntentOpList = runtime1.executeIntentOperations(opList);
 
 		// compile low-level intents into flow entry installation plan
 		PlanCalcRuntime runtime2 = new PlanCalcRuntime(g);
-		runtime2.addIntents(pathIntents);
+		runtime2.addIntents((PathIntentMap) runtime1.getPathIntents()); // TODO use pathIntentOpList
 
 		// show results
-		showResult(pathIntents);
+		showResult((PathIntentMap) runtime1.getPathIntents());
 		System.out.println(runtime2.getPlan());
 	}
 }