Implement compile method for SingleDstTreeFlow class.

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

Change-Id: I9133bf77711f3b02bce9099ddbaebe0fd89742c9
diff --git a/src/main/java/net/onrc/onos/api/flowmanager/SingleDstTreeFlow.java b/src/main/java/net/onrc/onos/api/flowmanager/SingleDstTreeFlow.java
index 60be78f..6fbed8a 100644
--- a/src/main/java/net/onrc/onos/api/flowmanager/SingleDstTreeFlow.java
+++ b/src/main/java/net/onrc/onos/api/flowmanager/SingleDstTreeFlow.java
@@ -1,59 +1,67 @@
 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.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 
 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.Dpid;
+import net.onrc.onos.core.util.PortNumber;
 import net.onrc.onos.core.util.SwitchPort;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 
 /**
- * A Flow object expressing the multipoints-to-point tree flow for the packet
- * layer.
+ * A {@link Flow} object expressing the multipoints-to-point tree flow for the
+ * packet layer.
  * <p>
- * NOTE: This class might generate the MatchActionPlan which includes the MAC
- * address modifications or other the label-switching-like schemes.
+ * NOTE: This class might generate the {@link MatchAction} operations which
+ * includes the MAC address modifications or other the label-switching-like
+ * schemes.
  */
 public class SingleDstTreeFlow extends Flow {
     private final PacketMatch match;
     private final Set<SwitchPort> ingressPorts;
     private final Tree tree;
-    private final List<Action> actions;
+    private final List<Action> egressActions;
 
     /**
      * Creates new instance using Tree object.
-     * <p>
-     * For now, the actions parameter must be the list of a single
-     * ModifyDstMacAction object and a single OutputAction object. But in the
-     * future, the parameter should accept any type of the list of IAction
-     * objects.
      *
      * @param id ID for this object
      * @param match the traffic filter for the tree
      * @param ingressPorts the set of ingress ports of the tree
      * @param tree the Tree object specifying tree topology for this object
-     * @param actions the list of Action objects at the egress edge switch
+     * @param egressActions the list of {@link Action} objects to be executed at
+     *        the egress edge switch
      */
     public SingleDstTreeFlow(FlowId id, PacketMatch match,
-            Collection<SwitchPort> ingressPorts, Tree tree, List<Action> actions) {
+            Collection<SwitchPort> ingressPorts, Tree tree, List<Action> egressActions) {
         super(id);
         this.match = checkNotNull(match);
         this.ingressPorts = ImmutableSet.copyOf(checkNotNull(ingressPorts));
         this.tree = checkNotNull(tree);
-        this.actions = ImmutableList.copyOf(checkNotNull(actions));
+        this.egressActions = ImmutableList.copyOf(checkNotNull(egressActions));
 
         // TODO: check if the tree is a MP2P tree.
-        // TODO: check consistency among ingressPorts, tree, and actions.
+        // TODO: check consistency between ingressPorts and tree topology.
     }
 
     /**
@@ -80,7 +88,7 @@
      * @return the list of actions at the egress edge switch
      */
     public List<Action> getEgressActions() {
-        return actions;
+        return egressActions;
     }
 
     @Override
@@ -92,7 +100,158 @@
     public List<MatchActionOperations> compile(Operator op,
             MatchActionIdGenerator maIdGenerator,
             MatchActionOperationsIdGenerator maoIdGenerator) {
+        switch (op) {
+        case ADD:
+            return compileAddOperation(maIdGenerator, maoIdGenerator);
+        case REMOVE:
+            return compileRemoveOperation();
+        default:
+            throw new UnsupportedOperationException("Unknown operation.");
+        }
+    }
+
+    private MatchAction createMatchAction(SwitchPort port, List<Action> actions,
+            MatchActionIdGenerator maIdGenerator) {
+        checkNotNull(port);
+        checkNotNull(actions);
+
+        return new MatchAction(maIdGenerator.getNewId(), port, getMatch(), actions);
+    }
+
+    /**
+     * Generates MatchAactionOperations at inner ports and at the egress switch.
+     *
+     * @param egressSwitch the egress switch of the tree
+     * @param inPorts a map of a set of incoming ports on each switch in the
+     *        tree
+     * @param outPorts a map of outgoing port on each switch in the tree
+     * @param maIdGenerator ID generator for MatchAction objects
+     * @param maoIdGenerator ID generator for MatchActionOperations objects
+     * @return the operations at inner ports and egress switch
+     */
+    private MatchActionOperations generateFirstAddOperations(
+            Dpid egressSwitch,
+            Map<Dpid, Set<PortNumber>> inPorts,
+            Map<Dpid, PortNumber> outPorts,
+            MatchActionIdGenerator maIdGenerator,
+            MatchActionOperationsIdGenerator maoIdGenerator) {
+        MatchActionOperations firstOps =
+                new MatchActionOperations(maoIdGenerator.getNewId());
+        for (Entry<Dpid, Set<PortNumber>> innerSw : inPorts.entrySet()) {
+            for (PortNumber innerPortNumber : innerSw.getValue()) {
+                SwitchPort innerPort = new SwitchPort(innerSw.getKey(), innerPortNumber);
+                MatchAction ma;
+                if (innerPort.getDpid().equals(egressSwitch)) {
+                    ma = createMatchAction(innerPort, getEgressActions(), maIdGenerator);
+                } else {
+                    PortNumber outputPortNumber = checkNotNull(
+                            outPorts.get(innerPort.getDpid()),
+                            String.format("The tree has no output port at %s",
+                                    innerPort.getDpid()));
+                    ma = createMatchAction(innerPort,
+                            Arrays.asList((Action) new OutputAction(outputPortNumber)),
+                            maIdGenerator);
+                }
+                firstOps.addOperation(new MatchActionOperationEntry(
+                        MatchActionOperations.Operator.ADD, ma));
+            }
+        }
+        return firstOps;
+    }
+
+    /**
+     * Generates MatchActionOperations for ingress switches in the tree.
+     *
+     * @param egressSwitch the egress switch of the tree
+     * @param outPorts a map of outgoing port on each switch in the tree
+     * @param maIdGenerator ID generator for MatchAction objects
+     * @param maoIdGenerator ID generator for MatchActionOperations objects
+     * @return operations at ingress switches in the tree
+     */
+    private MatchActionOperations generateSecondAddOperations(
+            Dpid egressSwitch,
+            Map<Dpid, PortNumber> outPorts,
+            MatchActionIdGenerator maIdGenerator,
+            MatchActionOperationsIdGenerator maoIdGenerator) {
+        MatchActionOperations secondOps =
+                new MatchActionOperations(maoIdGenerator.getNewId());
+        for (SwitchPort port : getIngressPorts()) {
+            PortNumber outputPort = outPorts.get(port.getDpid());
+            if (outputPort == null) {
+                if (port.getDpid().equals(egressSwitch)) {
+                    MatchAction ma = createMatchAction(
+                            port, getEgressActions(), maIdGenerator);
+                    secondOps.addOperation(new MatchActionOperationEntry(
+                            MatchActionOperations.Operator.ADD, ma));
+                } else {
+                    throw new IllegalStateException(String.format(
+                            "The switch %s specified as one of ingress ports "
+                                    + "does not have path to the egress switch.",
+                            port.getDpid()));
+                }
+            } else {
+                MatchAction ma = createMatchAction(port,
+                        Arrays.asList((Action) new OutputAction(outputPort)),
+                        maIdGenerator);
+                secondOps.addOperation(new MatchActionOperationEntry(
+                        MatchActionOperations.Operator.ADD, ma));
+            }
+        }
+        return secondOps;
+    }
+
+    private List<MatchActionOperations> compileAddOperation(
+            MatchActionIdGenerator maIdGenerator,
+            MatchActionOperationsIdGenerator maoIdGenerator) {
+        checkNotNull(tree);
+        checkState(tree.size() > 0, "Tree object has no link.");
+
+        // TODO: check consistency of the tree topology
+
+        // collect input ports and output ports checking consistency
+        Map<Dpid, PortNumber> outPorts = new HashMap<>();
+        Map<Dpid, Set<PortNumber>> inPorts = new HashMap<>();
+        for (FlowLink link : tree) {
+            SwitchPort srcPort = link.getSrcSwitchPort();
+            if (outPorts.containsKey(srcPort.getDpid())) {
+                throw new IllegalStateException(
+                        String.format("Dpid:%s has multiple output ports.",
+                                srcPort.getDpid()));
+            }
+            outPorts.put(srcPort.getDpid(), srcPort.getPortNumber());
+
+            SwitchPort dstPort = link.getDstSwitchPort();
+            Set<PortNumber> inPortNumbers = inPorts.get(dstPort.getDpid());
+            if (inPortNumbers == null) {
+                inPortNumbers = new HashSet<>();
+            }
+            inPortNumbers.add(dstPort.getPortNumber());
+            inPorts.put(dstPort.getDpid(), inPortNumbers);
+        }
+
+        // find the egress switch
+        Set<Dpid> egressSwitches = new HashSet<>(inPorts.keySet());
+        egressSwitches.removeAll(outPorts.keySet());
+        checkState(egressSwitches.size() == 1,
+                "The specified tree is not a single destination tree.");
+        Dpid egressSwitch = egressSwitches.iterator().next();
+
+        MatchActionOperations firstOps = generateFirstAddOperations(
+                egressSwitch, inPorts, outPorts, maIdGenerator, maoIdGenerator);
+        checkState(firstOps.size() > 0,
+                "No operations found for the first set of operations.");
+
+        MatchActionOperations secondOps = generateSecondAddOperations(
+                egressSwitch, outPorts, maIdGenerator, maoIdGenerator);
+        checkState(secondOps.size() > 0,
+                "No operations found for the second set of operations.");
+
+        return Arrays.asList(firstOps, secondOps);
+    }
+
+    private List<MatchActionOperations> compileRemoveOperation() {
         // TODO Auto-generated method stub
-        return null;
+        throw new UnsupportedOperationException(
+                "REMOVE operation is not implemented yet.");
     }
 }