ONOS-1048 - Define interfaces instead of using IntentManager

- Define IntentProcessor interface containing methods to process an intent
- Pull out IntentCompiler related tasks to CompilerRegistry
- Pull out IntentInstaller related tasks to InstallerRegistry
- Create an IntentProcessor subclass as inner class in IntentManager

Change-Id: Ia3e8d574a1053e7ddc9b961873ef758c9e0b1b26
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/IntentManager.java b/core/net/src/main/java/org/onosproject/net/intent/impl/IntentManager.java
index 8fa8b3f..3699df6 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/IntentManager.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/IntentManager.java
@@ -16,7 +16,6 @@
 package org.onosproject.net.intent.impl;
 
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -27,16 +26,13 @@
 import org.onosproject.core.IdGenerator;
 import org.onosproject.event.AbstractListenerRegistry;
 import org.onosproject.event.EventDeliveryService;
-import org.onosproject.net.flow.FlowRuleOperation;
 import org.onosproject.net.flow.FlowRuleOperations;
-import org.onosproject.net.flow.FlowRuleOperationsContext;
 import org.onosproject.net.flow.FlowRuleService;
 import org.onosproject.net.intent.Intent;
 import org.onosproject.net.intent.IntentBatchDelegate;
 import org.onosproject.net.intent.IntentCompiler;
 import org.onosproject.net.intent.IntentData;
 import org.onosproject.net.intent.IntentEvent;
-import org.onosproject.net.intent.IntentException;
 import org.onosproject.net.intent.IntentExtensionService;
 import org.onosproject.net.intent.IntentInstaller;
 import org.onosproject.net.intent.IntentListener;
@@ -53,29 +49,26 @@
 import org.onosproject.net.intent.impl.phase.Withdrawn;
 import org.slf4j.Logger;
 
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.EnumSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.Callable;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 import java.util.stream.Collectors;
 
 import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
 import static java.util.concurrent.Executors.newFixedThreadPool;
 import static java.util.concurrent.Executors.newSingleThreadExecutor;
 import static org.onlab.util.Tools.groupedThreads;
 import static org.onlab.util.Tools.isNullOrEmpty;
-import static org.onosproject.net.intent.IntentState.*;
+import static org.onosproject.net.intent.IntentState.FAILED;
+import static org.onosproject.net.intent.IntentState.INSTALL_REQ;
+import static org.onosproject.net.intent.IntentState.WITHDRAWN;
+import static org.onosproject.net.intent.IntentState.WITHDRAW_REQ;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -95,12 +88,6 @@
     private static final EnumSet<IntentState> RECOMPILE
             = EnumSet.of(INSTALL_REQ, FAILED, WITHDRAW_REQ);
 
-    // Collections for compiler, installer, and listener are ONOS instance local
-    private final ConcurrentMap<Class<? extends Intent>,
-            IntentCompiler<? extends Intent>> compilers = new ConcurrentHashMap<>();
-    private final ConcurrentMap<Class<? extends Intent>,
-            IntentInstaller<? extends Intent>> installers = new ConcurrentHashMap<>();
-
     private final AbstractListenerRegistry<IntentEvent, IntentListener>
             listenerRegistry = new AbstractListenerRegistry<>();
 
@@ -124,6 +111,9 @@
     private ExecutorService batchExecutor;
     private ExecutorService workerExecutor;
 
+    private final CompilerRegistry compilerRegistry = new CompilerRegistry();
+    private final InstallerRegistry installerRegistry = new InstallerRegistry();
+    private final InternalIntentProcessor processor = new InternalIntentProcessor();
     private final IntentStoreDelegate delegate = new InternalStoreDelegate();
     private final TopologyChangeDelegate topoDelegate = new InternalTopoChangeDelegate();
     private final IntentBatchDelegate batchDelegate = new InternalBatchDelegate();
@@ -211,259 +201,32 @@
 
     @Override
     public <T extends Intent> void registerCompiler(Class<T> cls, IntentCompiler<T> compiler) {
-        compilers.put(cls, compiler);
+        compilerRegistry.registerCompiler(cls, compiler);
     }
 
     @Override
     public <T extends Intent> void unregisterCompiler(Class<T> cls) {
-        compilers.remove(cls);
+        compilerRegistry.unregisterCompiler(cls);
     }
 
     @Override
     public Map<Class<? extends Intent>, IntentCompiler<? extends Intent>> getCompilers() {
-        return ImmutableMap.copyOf(compilers);
+        return compilerRegistry.getCompilers();
     }
 
     @Override
     public <T extends Intent> void registerInstaller(Class<T> cls, IntentInstaller<T> installer) {
-        installers.put(cls, installer);
+        installerRegistry.registerInstaller(cls, installer);
     }
 
     @Override
     public <T extends Intent> void unregisterInstaller(Class<T> cls) {
-        installers.remove(cls);
+        installerRegistry.unregisterInstaller(cls);
     }
 
     @Override
     public Map<Class<? extends Intent>, IntentInstaller<? extends Intent>> getInstallers() {
-        return ImmutableMap.copyOf(installers);
-    }
-
-    /**
-     * Returns the corresponding intent compiler to the specified intent.
-     *
-     * @param intent intent
-     * @param <T>    the type of intent
-     * @return intent compiler corresponding to the specified intent
-     */
-    private <T extends Intent> IntentCompiler<T> getCompiler(T intent) {
-        @SuppressWarnings("unchecked")
-        IntentCompiler<T> compiler = (IntentCompiler<T>) compilers.get(intent.getClass());
-        if (compiler == null) {
-            throw new IntentException("no compiler for class " + intent.getClass());
-        }
-        return compiler;
-    }
-
-    /**
-     * Returns the corresponding intent installer to the specified installable intent.
-     *
-     * @param intent intent
-     * @param <T>    the type of installable intent
-     * @return intent installer corresponding to the specified installable intent
-     */
-    private <T extends Intent> IntentInstaller<T> getInstaller(T intent) {
-        @SuppressWarnings("unchecked")
-        IntentInstaller<T> installer = (IntentInstaller<T>) installers.get(intent.getClass());
-        if (installer == null) {
-            throw new IntentException("no installer for class " + intent.getClass());
-        }
-        return installer;
-    }
-
-    /**
-     * Compiles an intent recursively.
-     *
-     * @param intent intent
-     * @param previousInstallables previous intent installables
-     * @return result of compilation
-     */
-    // TODO: make this non-public due to short term hack for ONOS-1051
-    public List<Intent> compileIntent(Intent intent, List<Intent> previousInstallables) {
-        if (intent.isInstallable()) {
-            return ImmutableList.of(intent);
-        }
-
-        registerSubclassCompilerIfNeeded(intent);
-        // FIXME: get previous resources
-        List<Intent> installable = new ArrayList<>();
-        for (Intent compiled : getCompiler(intent).compile(intent, previousInstallables, null)) {
-            installable.addAll(compileIntent(compiled, previousInstallables));
-        }
-        return installable;
-    }
-
-    //TODO javadoc
-    //FIXME
-    // TODO: make this non-public due to short term hack for ONOS-1051
-    public FlowRuleOperations coordinate(IntentData current, IntentData pending) {
-        List<Intent> oldInstallables = (current != null) ? current.installables() : null;
-        List<Intent> newInstallables = pending.installables();
-
-        checkState(isNullOrEmpty(oldInstallables) ||
-                   oldInstallables.size() == newInstallables.size(),
-                   "Old and New Intent must have equivalent installable intents.");
-
-        List<List<Collection<FlowRuleOperation>>> plans = new ArrayList<>();
-        for (int i = 0; i < newInstallables.size(); i++) {
-            Intent newInstallable = newInstallables.get(i);
-            registerSubclassInstallerIfNeeded(newInstallable);
-            //TODO consider migrating installers to FlowRuleOperations
-            /* FIXME
-               - we need to do another pass on this method about that doesn't
-               require the length of installables to be equal, and also doesn't
-               depend on ordering
-               - we should also reconsider when to start/stop tracking resources
-             */
-            if (isNullOrEmpty(oldInstallables)) {
-                plans.add(getInstaller(newInstallable).install(newInstallable));
-            } else {
-                Intent oldInstallable = oldInstallables.get(i);
-                checkState(oldInstallable.getClass().equals(newInstallable.getClass()),
-                           "Installable Intent type mismatch.");
-                trackerService.removeTrackedResources(pending.key(), oldInstallable.resources());
-                plans.add(getInstaller(newInstallable).replace(oldInstallable, newInstallable));
-            }
-            trackerService.addTrackedResources(pending.key(), newInstallable.resources());
-//            } catch (IntentException e) {
-//                log.warn("Unable to update intent {} due to:", oldIntent.id(), e);
-//                //FIXME... we failed. need to uninstall (if same) or revert (if different)
-//                trackerService.removeTrackedResources(newIntent.id(), newInstallable.resources());
-//                exception = e;
-//                batches = uninstallIntent(oldIntent, oldInstallables);
-//            }
-        }
-
-        return merge(plans).build(new FlowRuleOperationsContext() { // TODO move this out
-            @Override
-            public void onSuccess(FlowRuleOperations ops) {
-                log.debug("Completed installing: {}", pending.key());
-                pending.setState(INSTALLED);
-                store.write(pending);
-            }
-
-            @Override
-            public void onError(FlowRuleOperations ops) {
-                log.warn("Failed installation: {} {} on {}", pending.key(),
-                         pending.intent(), ops);
-                //TODO store.write(pending.setState(BROKEN));
-                pending.setState(FAILED);
-                store.write(pending);
-            }
-        });
-    }
-
-    /**
-     * Generate a {@link FlowRuleOperations} instance from the specified intent data.
-     *
-     * @param current intent data stored in the store
-     * @param pending intent data that is pending
-     * @return flow rule operations
-     */
-    // TODO: make this non-public due to short term hack for ONOS-1051
-    public FlowRuleOperations uninstallCoordinate(IntentData current, IntentData pending) {
-        List<Intent> installables = current.installables();
-        List<List<Collection<FlowRuleOperation>>> plans = new ArrayList<>();
-        for (Intent installable : installables) {
-            plans.add(getInstaller(installable).uninstall(installable));
-            trackerService.removeTrackedResources(pending.key(), installable.resources());
-        }
-
-        return merge(plans).build(new FlowRuleOperationsContext() {
-            @Override
-            public void onSuccess(FlowRuleOperations ops) {
-                log.debug("Completed withdrawing: {}", pending.key());
-                pending.setState(WITHDRAWN);
-                pending.setInstallables(Collections.emptyList());
-                store.write(pending);
-            }
-
-            @Override
-            public void onError(FlowRuleOperations ops) {
-                log.warn("Failed withdraw: {}", pending.key());
-                pending.setState(FAILED);
-                store.write(pending);
-            }
-        });
-    }
-
-
-    // TODO needs tests... or maybe it's just perfect
-    private FlowRuleOperations.Builder merge(List<List<Collection<FlowRuleOperation>>> plans) {
-        FlowRuleOperations.Builder builder = FlowRuleOperations.builder();
-        // Build a batch one stage at a time
-        for (int stageNumber = 0;; stageNumber++) {
-            // Get the sub-stage from each plan (List<Set<FlowRuleOperation>)
-            for (Iterator<List<Collection<FlowRuleOperation>>> itr = plans.iterator(); itr.hasNext();) {
-                List<Collection<FlowRuleOperation>> plan = itr.next();
-                if (plan.size() <= stageNumber) {
-                    // we have consumed all stages from this plan, so remove it
-                    itr.remove();
-                    continue;
-                }
-                // write operations from this sub-stage into the builder
-                Collection<FlowRuleOperation> stage = plan.get(stageNumber);
-                for (FlowRuleOperation entry : stage) {
-                    builder.operation(entry);
-                }
-            }
-            // we are done with the stage, start the next one...
-            if (plans.isEmpty()) {
-                break; // we don't need to start a new stage, we are done.
-            }
-            builder.newStage();
-        }
-        return builder;
-    }
-
-    /**
-     * Registers an intent compiler of the specified intent if an intent compiler
-     * for the intent is not registered. This method traverses the class hierarchy of
-     * the intent. Once an intent compiler for a parent type is found, this method
-     * registers the found intent compiler.
-     *
-     * @param intent intent
-     */
-    private void registerSubclassCompilerIfNeeded(Intent intent) {
-        if (!compilers.containsKey(intent.getClass())) {
-            Class<?> cls = intent.getClass();
-            while (cls != Object.class) {
-                // As long as we're within the Intent class descendants
-                if (Intent.class.isAssignableFrom(cls)) {
-                    IntentCompiler<?> compiler = compilers.get(cls);
-                    if (compiler != null) {
-                        compilers.put(intent.getClass(), compiler);
-                        return;
-                    }
-                }
-                cls = cls.getSuperclass();
-            }
-        }
-    }
-
-    /**
-     * Registers an intent installer of the specified intent if an intent installer
-     * for the intent is not registered. This method traverses the class hierarchy of
-     * the intent. Once an intent installer for a parent type is found, this method
-     * registers the found intent installer.
-     *
-     * @param intent intent
-     */
-    private void registerSubclassInstallerIfNeeded(Intent intent) {
-        if (!installers.containsKey(intent.getClass())) {
-            Class<?> cls = intent.getClass();
-            while (cls != Object.class) {
-                // As long as we're within the Intent class descendants
-                if (Intent.class.isAssignableFrom(cls)) {
-                    IntentInstaller<?> installer = installers.get(cls);
-                    if (installer != null) {
-                        installers.put(intent.getClass(), installer);
-                        return;
-                    }
-                }
-                cls = cls.getSuperclass();
-            }
-        }
+        return installerRegistry.getInstallers();
     }
 
     // Store delegate to re-post events emitted from the store.
@@ -525,12 +288,12 @@
         IntentData current = store.getIntentData(intentData.key());
         switch (intentData.state()) {
             case INSTALL_REQ:
-                return new InstallRequest(this, intentData, Optional.ofNullable(current));
+                return new InstallRequest(processor, intentData, Optional.ofNullable(current));
             case WITHDRAW_REQ:
                 if (current == null || isNullOrEmpty(current.installables())) {
                     return new Withdrawn(intentData, WITHDRAWN);
                 } else {
-                    return new WithdrawRequest(this, intentData, current);
+                    return new WithdrawRequest(processor, intentData, current);
                 }
             default:
                 // illegal state
@@ -648,4 +411,26 @@
             // TODO ensure that only one batch is in flight at a time
         }
     }
+
+    private class InternalIntentProcessor implements IntentProcessor {
+        @Override
+        public List<Intent> compile(Intent intent, List<Intent> previousInstallables) {
+            return compilerRegistry.compile(intent, previousInstallables);
+        }
+
+        @Override
+        public FlowRuleOperations coordinate(IntentData current, IntentData pending) {
+            return installerRegistry.coordinate(current, pending, store, trackerService);
+        }
+
+        @Override
+        public FlowRuleOperations uninstallCoordinate(IntentData current, IntentData pending) {
+            return installerRegistry.uninstallCoordinate(current, pending, store, trackerService);
+        }
+
+        @Override
+        public void applyFlowRules(FlowRuleOperations flowRules) {
+            flowRuleService.apply(flowRules);
+        }
+    }
 }