Implement compile method for PacketPathFlow class.

- This patch supports ADD operation only.
- This task is a part of ONOS-1741 and ONOS-1690.

Change-Id: Idd1cad5cf582df77633dae45b1fd5a9756708134
diff --git a/src/main/java/net/onrc/onos/api/flowmanager/PacketPathFlow.java b/src/main/java/net/onrc/onos/api/flowmanager/PacketPathFlow.java
index ce1fdb4..42cbd65 100644
--- a/src/main/java/net/onrc/onos/api/flowmanager/PacketPathFlow.java
+++ b/src/main/java/net/onrc/onos/api/flowmanager/PacketPathFlow.java
@@ -1,16 +1,24 @@
 package net.onrc.onos.api.flowmanager;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
 
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 
 import net.onrc.onos.api.flowmanager.FlowBatchOperation.Operator;
+import net.onrc.onos.core.matchaction.MatchAction;
 import net.onrc.onos.core.matchaction.MatchActionIdGenerator;
+import net.onrc.onos.core.matchaction.MatchActionOperationEntry;
 import net.onrc.onos.core.matchaction.MatchActionOperations;
 import net.onrc.onos.core.matchaction.MatchActionOperationsIdGenerator;
 import net.onrc.onos.core.matchaction.action.Action;
+import net.onrc.onos.core.matchaction.action.OutputAction;
 import net.onrc.onos.core.matchaction.match.PacketMatch;
 import net.onrc.onos.core.util.PortNumber;
+import net.onrc.onos.core.util.SwitchPort;
 
 /**
  * Flow object representing a packet path.
@@ -70,7 +78,105 @@
     public List<MatchActionOperations> compile(Operator op,
             MatchActionIdGenerator maIdGenerator,
             MatchActionOperationsIdGenerator maoIdGenerator) {
-        // TODO Auto-generated method stub
-        return null;
+        switch (op) {
+        case ADD:
+            return compileAddOperation(maIdGenerator, maoIdGenerator);
+        case REMOVE:
+            return compileRemoveOperation(maIdGenerator, maoIdGenerator);
+        default:
+            throw new UnsupportedOperationException("Unknown operation.");
+        }
+    }
+
+    /**
+     * Creates the next {@link MatchAction} object using iterators.
+     *
+     * @param portIterator the iterator for {@link SwitchPort} objects
+     * @param actionsIterator the iterator for the lists of {@link Action}
+     * @param maIdGenerator the ID generator of {@link MatchAction}
+     * @return {@link MatchAction} object based on the specified iterators
+     */
+    private MatchAction createNextMatchAction(Iterator<SwitchPort> portIterator,
+            Iterator<List<Action>> actionsIterator,
+            MatchActionIdGenerator maIdGenerator) {
+        if (portIterator == null || actionsIterator == null ||
+                !portIterator.hasNext() || !actionsIterator.hasNext()) {
+            return null;
+        }
+
+        // TODO: Update this after merging the new MatchAction API.
+        return new MatchAction(
+                maIdGenerator.getNewId(),
+                portIterator.next(),
+                getMatch(), actionsIterator.next());
+    }
+
+    /**
+     * Generates the list of {@link MatchActionOperations} objects with
+     * add-operation.
+     *
+     * @return the list of {@link MatchActionOperations} objects
+     */
+    private List<MatchActionOperations> compileAddOperation(
+            MatchActionIdGenerator maIdGenerator,
+            MatchActionOperationsIdGenerator maoIdGenerator) {
+        Path path = checkNotNull(getPath());
+        checkState(path.size() > 0, "Path object has no link.");
+
+        // Preparing each actions and ingress port for each switch
+        List<List<Action>> actionsList = new LinkedList<>();
+        List<SwitchPort> portList = new LinkedList<>();
+        for (FlowLink link : path) {
+            portList.add(link.getDstSwitchPort());
+            actionsList.add(Arrays.asList(
+                    (Action) new OutputAction(link.getSrcPortNumber())));
+        }
+
+        // The head switch's ingress port
+        portList.add(0, new SwitchPort(path.getSrcDpid(), getIngressPortNumber()));
+
+        // The tail switch's action
+        actionsList.add(getEgressActions());
+
+        Iterator<SwitchPort> portIterator = portList.iterator();
+        Iterator<List<Action>> actionsIterator = actionsList.iterator();
+
+        // Creates the second phase operation
+        // using the head switch's match action
+        MatchAction headMatchAction = createNextMatchAction(portIterator,
+                actionsIterator, maIdGenerator);
+        if (headMatchAction == null) {
+            return null;
+        }
+        MatchActionOperations secondOp = new MatchActionOperations(
+                maoIdGenerator.getNewId());
+        secondOp.addOperation(new MatchActionOperationEntry(
+                MatchActionOperations.Operator.ADD, headMatchAction));
+
+        // Creates the first phase operation
+        // using the remaining switches' match actions
+        MatchActionOperations firstOp = new MatchActionOperations(
+                maoIdGenerator.getNewId());
+        MatchAction ma;
+        while ((ma = createNextMatchAction(portIterator, actionsIterator, maIdGenerator)) != null) {
+            firstOp.addOperation(new MatchActionOperationEntry(
+                    MatchActionOperations.Operator.ADD, ma));
+        }
+
+        return Arrays.asList(firstOp, secondOp);
+    }
+
+    /**
+     * Generates the list of {@link MatchActionOperations} objects with
+     * remote-operation.
+     *
+     * @return the list of {@link MatchActionOperations} objects
+     */
+    private List<MatchActionOperations> compileRemoveOperation(
+            MatchActionIdGenerator maIdGenerator,
+            MatchActionOperationsIdGenerator maoIdGenerator) {
+        // TODO implement it
+        throw new UnsupportedOperationException(
+                "REMOVE operation is not implemented yet.");
     }
 }
diff --git a/src/test/java/net/onrc/onos/api/flowmanager/PacketPathFlowTest.java b/src/test/java/net/onrc/onos/api/flowmanager/PacketPathFlowTest.java
new file mode 100644
index 0000000..04e2649
--- /dev/null
+++ b/src/test/java/net/onrc/onos/api/flowmanager/PacketPathFlowTest.java
@@ -0,0 +1,234 @@
+package net.onrc.onos.api.flowmanager;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.util.Arrays;
+import java.util.List;
+
+import net.floodlightcontroller.util.MACAddress;
+import net.onrc.onos.core.matchaction.MatchAction;
+import net.onrc.onos.core.matchaction.MatchActionIdGeneratorWithIdBlockAllocator;
+import net.onrc.onos.core.matchaction.MatchActionOperationEntry;
+import net.onrc.onos.core.matchaction.MatchActionOperations;
+import net.onrc.onos.core.matchaction.MatchActionOperations.Operator;
+import net.onrc.onos.core.matchaction.MatchActionOperationsIdGeneratorWithIdBlockAllocator;
+import net.onrc.onos.core.matchaction.action.Action;
+import net.onrc.onos.core.matchaction.action.ModifyDstMacAction;
+import net.onrc.onos.core.matchaction.action.OutputAction;
+import net.onrc.onos.core.matchaction.match.PacketMatch;
+import net.onrc.onos.core.matchaction.match.PacketMatchBuilder;
+import net.onrc.onos.core.util.IdBlock;
+import net.onrc.onos.core.util.IdBlockAllocator;
+import net.onrc.onos.core.util.PortNumber;
+import net.onrc.onos.core.util.SwitchPort;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link PacketPathFlow} class.
+ */
+public class PacketPathFlowTest {
+    private Path pathWith4Switches;
+    private Path pathWith2Switches;
+    private PacketMatch match;
+    private List<Action> actions;
+    private IdBlockAllocator allocator;
+
+    @Before
+    public void setUp() throws Exception {
+        allocator = createMock(IdBlockAllocator.class);
+        expect(allocator.allocateUniqueIdBlock())
+                .andReturn(new IdBlock(0, 99))
+                .andReturn(new IdBlock(100, 199));
+        replay(allocator);
+
+        pathWith4Switches = new Path();
+        pathWith4Switches.add(new FlowLink(
+                new SwitchPort(1, (short) 10), new SwitchPort(2, (short) 11)));
+        pathWith4Switches.add(new FlowLink(
+                new SwitchPort(2, (short) 12), new SwitchPort(3, (short) 13)));
+        pathWith4Switches.add(new FlowLink(
+                new SwitchPort(3, (short) 14), new SwitchPort(4, (short) 15)));
+
+        pathWith2Switches = new Path();
+        pathWith2Switches.add(new FlowLink(
+                new SwitchPort(1, (short) 10), new SwitchPort(2, (short) 11)));
+
+        PacketMatchBuilder builder = new PacketMatchBuilder();
+        builder.setDstMac(MACAddress.valueOf(54321));
+        match = builder.build();
+
+        actions = Arrays.asList(
+                new ModifyDstMacAction(MACAddress.valueOf(12345)),
+                new OutputAction(PortNumber.uint32(101)));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+    }
+
+    /**
+     * Checks the constructor initializes fields properly.
+     */
+    @Test
+    public void testConstructor() {
+        PacketPathFlow flow = new PacketPathFlow(
+                new FlowId(1L), match, PortNumber.uint32(100),
+                pathWith4Switches, actions, 0, 0);
+
+        assertNotNull(flow);
+        assertEquals(new FlowId(1L), flow.getId());
+        assertEquals(match, flow.getMatch());
+        assertEquals(PortNumber.uint32(100), flow.getIngressPortNumber());
+        assertEquals(pathWith4Switches, flow.getPath());
+        assertEquals(actions, flow.getEgressActions());
+        assertEquals(0, flow.getHardTimeout());
+        assertEquals(0, flow.getIdleTimeout());
+    }
+
+    /**
+     * Checks the compile method with add-operation. This test creates a flow
+     * object using the path with 2 switches, then checks if the compiled the
+     * list of MatchActionOperations objects are generated properly.
+     */
+    @Test
+    public void testCompileWithAddOperationFor2Switches() {
+        PacketPathFlow flow = new PacketPathFlow(
+                new FlowId(1L), match, PortNumber.uint32(100),
+                pathWith2Switches, actions, 0, 0);
+
+        List<MatchActionOperations> maOpsList =
+                flow.compile(FlowBatchOperation.Operator.ADD,
+                        new MatchActionIdGeneratorWithIdBlockAllocator(allocator),
+                        new MatchActionOperationsIdGeneratorWithIdBlockAllocator(
+                                allocator)
+                        );
+
+        assertEquals(2, maOpsList.size());
+
+        MatchActionOperations firstOp = maOpsList.get(0);
+        assertEquals(1, firstOp.size());
+        assertEquals(1, firstOp.getOperations().size());
+
+        MatchActionOperations secondOp = maOpsList.get(1);
+        assertEquals(1, secondOp.size());
+        assertEquals(1, secondOp.getOperations().size());
+
+        MatchActionOperationEntry entry1 = secondOp.getOperations().get(0);
+        MatchActionOperationEntry entry2 = firstOp.getOperations().get(0);
+
+        assertEquals(Operator.ADD, entry1.getOperator());
+        assertEquals(Operator.ADD, entry2.getOperator());
+
+        MatchAction ma1 = entry1.getTarget();
+        MatchAction ma2 = entry2.getTarget();
+
+        assertNotNull(ma1);
+        assertNotNull(ma2);
+
+        assertEquals(new SwitchPort(1, (short) 100), ma1.getSwitchPort());
+        assertEquals(new SwitchPort(2, (short) 11), ma2.getSwitchPort());
+
+        assertEquals(match, ma1.getMatch());
+        assertEquals(match, ma2.getMatch());
+
+        assertNotNull(ma1.getActions());
+        assertNotNull(ma2.getActions());
+
+        assertEquals(1, ma1.getActions().size());
+        assertEquals(2, ma2.getActions().size());
+
+        assertEquals(new OutputAction(PortNumber.uint32(10)),
+                ma1.getActions().get(0));
+        assertEquals(new ModifyDstMacAction(MACAddress.valueOf(12345)),
+                ma2.getActions().get(0));
+        assertEquals(new OutputAction(PortNumber.uint32(101)),
+                ma2.getActions().get(1));
+    }
+
+    /**
+     * Checks the compile method with add-operation. This test creates a flow
+     * object using the path with 4 switches, then checks if the compiled the
+     * list of MatchActionOperations objects are generated properly.
+     */
+    @Test
+    public void testCompileWithAddOperationFor4Switches() {
+        PacketPathFlow flow = new PacketPathFlow(
+                new FlowId(1L), match, PortNumber.uint32(100),
+                pathWith4Switches, actions, 0, 0);
+
+        List<MatchActionOperations> maOpsList =
+                flow.compile(FlowBatchOperation.Operator.ADD,
+                        new MatchActionIdGeneratorWithIdBlockAllocator(allocator),
+                        new MatchActionOperationsIdGeneratorWithIdBlockAllocator(
+                                allocator)
+                        );
+
+        assertEquals(2, maOpsList.size());
+
+        MatchActionOperations firstOp = maOpsList.get(0);
+        assertEquals(3, firstOp.size());
+        assertEquals(3, firstOp.getOperations().size());
+
+        MatchActionOperations secondOp = maOpsList.get(1);
+        assertEquals(1, secondOp.size());
+        assertEquals(1, secondOp.getOperations().size());
+
+        MatchActionOperationEntry entry1 = secondOp.getOperations().get(0);
+        MatchActionOperationEntry entry2 = firstOp.getOperations().get(0);
+        MatchActionOperationEntry entry3 = firstOp.getOperations().get(1);
+        MatchActionOperationEntry entry4 = firstOp.getOperations().get(2);
+
+        assertEquals(Operator.ADD, entry1.getOperator());
+        assertEquals(Operator.ADD, entry2.getOperator());
+        assertEquals(Operator.ADD, entry3.getOperator());
+        assertEquals(Operator.ADD, entry4.getOperator());
+
+        MatchAction ma1 = entry1.getTarget();
+        MatchAction ma2 = entry2.getTarget();
+        MatchAction ma3 = entry3.getTarget();
+        MatchAction ma4 = entry4.getTarget();
+
+        assertNotNull(ma1);
+        assertNotNull(ma2);
+        assertNotNull(ma3);
+        assertNotNull(ma4);
+
+        assertEquals(new SwitchPort(1, (short) 100), ma1.getSwitchPort());
+        assertEquals(new SwitchPort(2, (short) 11), ma2.getSwitchPort());
+        assertEquals(new SwitchPort(3, (short) 13), ma3.getSwitchPort());
+        assertEquals(new SwitchPort(4, (short) 15), ma4.getSwitchPort());
+
+        assertEquals(match, ma1.getMatch());
+        assertEquals(match, ma2.getMatch());
+        assertEquals(match, ma3.getMatch());
+        assertEquals(match, ma4.getMatch());
+
+        assertNotNull(ma1.getActions());
+        assertNotNull(ma2.getActions());
+        assertNotNull(ma3.getActions());
+        assertNotNull(ma4.getActions());
+
+        assertEquals(1, ma1.getActions().size());
+        assertEquals(1, ma2.getActions().size());
+        assertEquals(1, ma3.getActions().size());
+        assertEquals(2, ma4.getActions().size());
+
+        assertEquals(new OutputAction(PortNumber.uint32(10)),
+                ma1.getActions().get(0));
+        assertEquals(new OutputAction(PortNumber.uint32(12)),
+                ma2.getActions().get(0));
+        assertEquals(new OutputAction(PortNumber.uint32(14)),
+                ma3.getActions().get(0));
+        assertEquals(new ModifyDstMacAction(MACAddress.valueOf(12345)),
+                ma4.getActions().get(0));
+        assertEquals(new OutputAction(PortNumber.uint32(101)),
+                ma4.getActions().get(1));
+    }
+}