Implement IntentCompilers

For the following Intent types

- PointToPointIntent
- PathIntent
- MultiPointToSinglePointIntent

This resolves ONOS-1657, ONOS-1659, and ONOS-1660.

Change-Id: I7c68cec4b025a59f9c97bae4488801bea2716baf
diff --git a/src/main/java/net/onrc/onos/core/newintent/AbstractFlowGeneratingIntentCompiler.java b/src/main/java/net/onrc/onos/core/newintent/AbstractFlowGeneratingIntentCompiler.java
new file mode 100644
index 0000000..07041e9
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/newintent/AbstractFlowGeneratingIntentCompiler.java
@@ -0,0 +1,41 @@
+package net.onrc.onos.core.newintent;
+
+import net.onrc.onos.api.flowmanager.FlowId;
+import net.onrc.onos.api.flowmanager.FlowIdGenerator;
+import net.onrc.onos.api.newintent.Intent;
+import net.onrc.onos.api.newintent.IntentIdGenerator;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A base class of {@link net.onrc.onos.api.newintent.IntentCompiler},
+ * which generates Flow objects.
+ * @param <T>
+ */
+public abstract class AbstractFlowGeneratingIntentCompiler<T extends Intent>
+        extends AbstractIntentCompiler<T> {
+
+    private final FlowIdGenerator flowIdGenerator;
+
+    /**
+     * Constructs an object with the specified {@link IntentIdGenerator}
+     * and {@link FlowIdGenerator}.
+     *
+     * @param intentIdGenerator
+     * @param flowIdGenerator
+     */
+    protected AbstractFlowGeneratingIntentCompiler(IntentIdGenerator intentIdGenerator,
+                                                   FlowIdGenerator flowIdGenerator) {
+        super(intentIdGenerator);
+        this.flowIdGenerator = checkNotNull(flowIdGenerator);
+    }
+
+    /**
+     * Returns the next {@link FlowId}.
+     *
+     * @return the next {@link FlowId}
+     */
+    protected FlowId getNextFlowId() {
+        return flowIdGenerator.getNewId();
+    }
+}
diff --git a/src/main/java/net/onrc/onos/core/newintent/AbstractIntentCompiler.java b/src/main/java/net/onrc/onos/core/newintent/AbstractIntentCompiler.java
new file mode 100644
index 0000000..0b16f10
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/newintent/AbstractIntentCompiler.java
@@ -0,0 +1,54 @@
+package net.onrc.onos.core.newintent;
+
+import net.onrc.onos.api.newintent.ConnectivityIntent;
+import net.onrc.onos.api.newintent.Intent;
+import net.onrc.onos.api.newintent.IntentCompiler;
+import net.onrc.onos.api.newintent.IntentId;
+import net.onrc.onos.api.newintent.IntentIdGenerator;
+import net.onrc.onos.core.matchaction.action.Action;
+import net.onrc.onos.core.matchaction.action.Actions;
+import net.onrc.onos.core.matchaction.action.OutputAction;
+import net.onrc.onos.core.util.SwitchPort;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A base IntentCompiler implementation.
+ * @param <T> the type of intent
+ */
+public abstract class AbstractIntentCompiler<T extends Intent> implements IntentCompiler<T> {
+    private final IntentIdGenerator idGenerator;
+
+    /**
+     * Constructs an instance with the specified Intent ID generator.
+     * <p>
+     * Intent compiler generates intents from an input intent.
+     * To make sure to use unique IDs for generated intents, intent
+     * ID generator is given as the argument of a constructor in normal
+     * cases.
+     * </p>
+     * @param idGenerator intent ID generator
+     */
+    protected AbstractIntentCompiler(IntentIdGenerator idGenerator) {
+        this.idGenerator = checkNotNull(idGenerator);
+    }
+
+    protected IntentId getNextId() {
+        return idGenerator.getNewId();
+    }
+
+    protected List<Action> packActions(ConnectivityIntent intent, SwitchPort egress) {
+        List<Action> actions = new ArrayList<>();
+        Action intentAction = intent.getAction();
+        if (!intentAction.equals(Actions.nullAction())) {
+            actions.add(intentAction);
+        }
+
+        OutputAction output = new OutputAction(egress.getPortNumber());
+        actions.add(output);
+        return actions;
+    }
+}
diff --git a/src/main/java/net/onrc/onos/core/newintent/IntentCompilationException.java b/src/main/java/net/onrc/onos/core/newintent/IntentCompilationException.java
new file mode 100644
index 0000000..23e7c72
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/newintent/IntentCompilationException.java
@@ -0,0 +1,22 @@
+package net.onrc.onos.core.newintent;
+
+import net.onrc.onos.api.newintent.IntentException;
+
+/**
+ * An exception thrown when a intent compilation fails.
+ */
+public class IntentCompilationException extends IntentException {
+    private static final long serialVersionUID = 235237603018210810L;
+
+    public IntentCompilationException() {
+        super();
+    }
+
+    public IntentCompilationException(String message) {
+        super(message);
+    }
+
+    public IntentCompilationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/src/main/java/net/onrc/onos/core/newintent/MultiPointToSinglePointIntentCompiler.java b/src/main/java/net/onrc/onos/core/newintent/MultiPointToSinglePointIntentCompiler.java
new file mode 100644
index 0000000..eb31457
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/newintent/MultiPointToSinglePointIntentCompiler.java
@@ -0,0 +1,111 @@
+package net.onrc.onos.core.newintent;
+
+import net.onrc.onos.api.flowmanager.FlowIdGenerator;
+import net.onrc.onos.api.flowmanager.SingleDstTreeFlow;
+import net.onrc.onos.api.flowmanager.Tree;
+import net.onrc.onos.api.newintent.Intent;
+import net.onrc.onos.api.newintent.IntentIdGenerator;
+import net.onrc.onos.api.newintent.MultiPointToSinglePointIntent;
+import net.onrc.onos.core.intent.ConstrainedBFSTree;
+import net.onrc.onos.core.intent.Path;
+import net.onrc.onos.core.matchaction.match.Match;
+import net.onrc.onos.core.matchaction.match.PacketMatch;
+import net.onrc.onos.core.topology.BaseTopology;
+import net.onrc.onos.core.topology.ITopologyService;
+import net.onrc.onos.core.topology.Switch;
+import net.onrc.onos.core.util.SwitchPort;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static net.onrc.onos.core.newintent.PointToPointIntentCompiler.convertPath;
+
+/**
+ * An intent compiler for {@link MultiPointToSinglePointIntent}.
+ */
+public class MultiPointToSinglePointIntentCompiler
+        extends AbstractFlowGeneratingIntentCompiler<MultiPointToSinglePointIntent> {
+
+    private final ITopologyService topologyService;
+
+    /**
+     * Constructs an intent compiler for {@link MultiPointToSinglePointIntent}.
+     *
+     * @param intentIdGenerator intent ID generator
+     * @param flowIdGenerator flow ID generator
+     * @param topologyService topology service
+     */
+    public MultiPointToSinglePointIntentCompiler(IntentIdGenerator intentIdGenerator,
+                                                 FlowIdGenerator flowIdGenerator,
+                                                 ITopologyService topologyService) {
+        super(intentIdGenerator, flowIdGenerator);
+        this.topologyService = checkNotNull(topologyService);
+    }
+
+    @Override
+    public List<Intent> compile(MultiPointToSinglePointIntent intent) {
+        Match match = intent.getMatch();
+        if (!(match instanceof PacketMatch)) {
+            throw new IntentCompilationException(
+                    "intent has unsupported type of match object: " + match
+            );
+        }
+
+        SingleDstTreeFlow treeFlow = new SingleDstTreeFlow(
+                getNextFlowId(),
+                (PacketMatch) intent.getMatch(), // down-cast, but it is safe due to the guard above
+                intent.getIngressPorts(),
+                calculateTree(intent.getIngressPorts(), intent.getEgressPort()),
+                packActions(intent, intent.getEgressPort())
+        );
+        Intent compiled = new SingleDstTreeFlowIntent(getNextId(), treeFlow);
+        return Arrays.asList(compiled);
+    }
+
+    /**
+     * Calculates a tree with the specified ingress ports and egress port.
+     *
+     * {@link PathNotFoundException} is thrown when no tree found or
+     * the specified egress port is not found in the topology.
+     *
+     * @param ingressPorts ingress ports
+     * @param egressPort egress port
+     * @return tree
+     * @throws PathNotFoundException if the specified egress switch is not
+     * found or no tree is found.
+     */
+    private Tree calculateTree(Set<SwitchPort> ingressPorts, SwitchPort egressPort) {
+        BaseTopology topology = topologyService.getTopology();
+        Switch egressSwitch = topology.getSwitch(egressPort.getDpid());
+        if (egressSwitch == null) {
+            throw new PathNotFoundException("destination switch not found: " + egressPort.getDpid());
+        }
+
+        ConstrainedBFSTree bfs = new ConstrainedBFSTree(egressSwitch);
+        Tree tree = new Tree();
+
+        for (SwitchPort ingressPort: ingressPorts) {
+            Switch ingressSwitch = topology.getSwitch(ingressPort.getDpid());
+            if (ingressSwitch == null) {
+                continue;
+            }
+
+            Path path = bfs.getPath(ingressSwitch);
+            if (path.isEmpty()) {
+                continue;
+            }
+
+            tree.addAll(convertPath(path));
+        }
+
+        if (tree.isEmpty()) {
+            throw new PathNotFoundException(
+                    String.format("No tree found (ingress: %s, egress: %s", ingressPorts, egressPort)
+            );
+        }
+
+        return tree;
+    }
+}
diff --git a/src/main/java/net/onrc/onos/core/newintent/PathIntentCompiler.java b/src/main/java/net/onrc/onos/core/newintent/PathIntentCompiler.java
new file mode 100644
index 0000000..e7e2f87
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/newintent/PathIntentCompiler.java
@@ -0,0 +1,82 @@
+package net.onrc.onos.core.newintent;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import net.onrc.onos.api.flowmanager.FlowIdGenerator;
+import net.onrc.onos.api.flowmanager.FlowLink;
+import net.onrc.onos.api.flowmanager.PacketPathFlow;
+import net.onrc.onos.api.flowmanager.Path;
+import net.onrc.onos.api.newintent.Intent;
+import net.onrc.onos.api.newintent.IntentIdGenerator;
+import net.onrc.onos.api.newintent.PathIntent;
+import net.onrc.onos.core.matchaction.match.Match;
+import net.onrc.onos.core.matchaction.match.PacketMatch;
+import net.onrc.onos.core.util.LinkTuple;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * An intent compiler for {@link PathIntent}.
+ */
+public class PathIntentCompiler
+        extends AbstractFlowGeneratingIntentCompiler<PathIntent> {
+
+    /**
+     * Construct an {@link net.onrc.onos.api.newintent.IntentCompiler}
+     * for {@link PathIntent}.
+     *
+     * @param intentIdGenerator intent ID generator
+     * @param flowIdGenerator flow ID generator
+     */
+    public PathIntentCompiler(IntentIdGenerator intentIdGenerator,
+                              FlowIdGenerator flowIdGenerator) {
+        super(intentIdGenerator, flowIdGenerator);
+    }
+
+    @Override
+    public List<Intent> compile(PathIntent intent) {
+        Match match = intent.getMatch();
+        if (!(match instanceof PacketMatch)) {
+            throw new IntentCompilationException(
+                    "intent has unsupported type of match object: " + match
+            );
+        }
+
+        Path path = convertPath(intent.getPath());
+        PacketPathFlow flow = new PacketPathFlow(
+                getNextFlowId(),
+                (PacketMatch) match,
+                intent.getIngressPort().getPortNumber(),
+                path,
+                packActions(intent, intent.getEgressPort()),
+                0, 0
+        );
+        Intent compiled = new PathFlowIntent(getNextId(), flow);
+        return Arrays.asList(compiled);
+    }
+
+    /**
+     * Converts list of {@link LinkTuple LinkTuples} to a {@link Path}.
+     *
+     * @param tuples original list of {@link LinkTuple LinkTuples}
+     * @return converted {@link Path}
+     */
+    private Path convertPath(List<LinkTuple> tuples) {
+        // would like to use filter and transform, but Findbugs detects
+        // inconsistency of use of @Nullable annotation. Then, use of the
+        // transform is avoided.
+        // Ref: https://code.google.com/p/guava-libraries/issues/detail?id=1812
+        // TODO: replace with transform when the above issue is resolved
+        ImmutableList<LinkTuple> links = FluentIterable.from(tuples)
+                .filter(Predicates.notNull())
+                .toList();
+
+        Path path = new Path();
+        for (LinkTuple link: links) {
+            path.add(new FlowLink(link.getSrc(), link.getDst()));
+        }
+        return path;
+    }
+}
diff --git a/src/main/java/net/onrc/onos/core/newintent/PathNotFoundException.java b/src/main/java/net/onrc/onos/core/newintent/PathNotFoundException.java
new file mode 100644
index 0000000..f08636e
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/newintent/PathNotFoundException.java
@@ -0,0 +1,22 @@
+package net.onrc.onos.core.newintent;
+
+import net.onrc.onos.api.newintent.IntentException;
+
+/**
+ * An exception thrown when a path is not found.
+ */
+public class PathNotFoundException extends IntentException {
+    private static final long serialVersionUID = -2087045731049914733L;
+
+    public PathNotFoundException() {
+        super();
+    }
+
+    public PathNotFoundException(String message) {
+        super(message);
+    }
+
+    public PathNotFoundException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/src/main/java/net/onrc/onos/core/newintent/PointToPointIntentCompiler.java b/src/main/java/net/onrc/onos/core/newintent/PointToPointIntentCompiler.java
new file mode 100644
index 0000000..fcf718d
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/newintent/PointToPointIntentCompiler.java
@@ -0,0 +1,117 @@
+package net.onrc.onos.core.newintent;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import net.onrc.onos.api.flowmanager.FlowId;
+import net.onrc.onos.api.flowmanager.FlowIdGenerator;
+import net.onrc.onos.api.flowmanager.FlowLink;
+import net.onrc.onos.api.flowmanager.PacketPathFlow;
+import net.onrc.onos.api.flowmanager.Path;
+import net.onrc.onos.api.newintent.Intent;
+import net.onrc.onos.api.newintent.IntentIdGenerator;
+import net.onrc.onos.api.newintent.PointToPointIntent;
+import net.onrc.onos.core.intent.ConstrainedBFSTree;
+import net.onrc.onos.core.matchaction.action.Action;
+import net.onrc.onos.core.matchaction.match.Match;
+import net.onrc.onos.core.matchaction.match.PacketMatch;
+import net.onrc.onos.core.topology.BaseTopology;
+import net.onrc.onos.core.topology.ITopologyService;
+import net.onrc.onos.core.topology.LinkEvent;
+import net.onrc.onos.core.topology.Switch;
+import net.onrc.onos.core.util.SwitchPort;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A intent compiler for {@link PointToPointIntent}.
+ */
+public class PointToPointIntentCompiler
+        extends AbstractFlowGeneratingIntentCompiler<PointToPointIntent> {
+
+    private final ITopologyService topologyService;
+
+    /**
+     * Constructs an intent compiler for {@link PointToPointIntent} with the specified
+     * ID generator and topology service.
+     *
+     * @param intentIdGenerator intent ID generator
+     * @param topologyService topology service
+     */
+    public PointToPointIntentCompiler(IntentIdGenerator intentIdGenerator,
+                                      FlowIdGenerator flowIdGenerator,
+                                      ITopologyService topologyService) {
+        super(intentIdGenerator, flowIdGenerator);
+        this.topologyService = checkNotNull(topologyService);
+    }
+
+    @Override
+    public List<Intent> compile(PointToPointIntent intent) {
+        Match match = intent.getMatch();
+        if (!(match instanceof PacketMatch)) {
+            throw new IntentCompilationException(
+                    "intent has unsupported type of match object: " + match
+            );
+        }
+
+        SwitchPort ingress = intent.getIngressPort();
+        SwitchPort egress = intent.getEgressPort();
+        FlowId flowId = getNextFlowId();
+        Path path = calculatePath(ingress, egress);
+
+        List<Action> actions = packActions(intent, intent.getEgressPort());
+
+        PacketPathFlow flow = new PacketPathFlow(flowId, (PacketMatch) match,
+                ingress.getPortNumber(), path, actions, 0, 0);
+        return Arrays.asList((Intent) new PathFlowIntent(getNextId(), flow));
+    }
+
+    /**
+     * Calculates a path between the specified ingress port and the specified egress port.
+     * @param ingress ingress port
+     * @param egress egress port
+     * @return path
+     */
+    private Path calculatePath(SwitchPort ingress, SwitchPort egress) {
+        BaseTopology topology = topologyService.getTopology();
+        Switch source = topology.getSwitch(ingress.getDpid());
+        Switch destination = topology.getSwitch(egress.getDpid());
+
+        if (source == null) {
+            throw new PathNotFoundException("source switch not found: " + ingress.getDpid());
+        }
+        if (destination == null) {
+            throw new PathNotFoundException("destination switch not found: " + egress.getDpid());
+        }
+
+        ConstrainedBFSTree tree = new ConstrainedBFSTree(source);
+        net.onrc.onos.core.intent.Path path = tree.getPath(destination);
+        return convertPath(path);
+    }
+
+    /**
+     * Converts a {@link net.onrc.onos.core.intent.Path} to {@link Path}.
+     *
+     * @param path original {@link net.onrc.onos.core.intent.Path}
+     * @return converted {@link Path}
+     */
+    static Path convertPath(net.onrc.onos.core.intent.Path path) {
+        // would like to use filter and transform, but Findbugs detects
+        // inconsistency of use of @Nullable annotation. Then, use of the
+        // transform is avoided.
+        // Ref: https://code.google.com/p/guava-libraries/issues/detail?id=1812
+        // TODO: replace with transform when the above issue is resolved
+        ImmutableList<LinkEvent> events = FluentIterable.from(path)
+                .filter(Predicates.notNull())
+                .toList();
+
+        Path converted = new Path();
+        for (LinkEvent event: events) {
+            converted.add(new FlowLink(event.getSrc(), event.getDst()));
+        }
+        return converted;
+    }
+}