Promote BindingRuntimeContext to binding-generator-api
[mdsal.git] / binding / mdsal-binding-generator-impl / src / main / java / org / opendaylight / mdsal / binding / generator / impl / ModuleInfoBackedContext.java
index 742f3661c3c7dc66df7424c7076013eb4c05c58d..75cefbf11c14d1def8cd795dd6dcb4f9d40fe369 100644 (file)
@@ -8,20 +8,32 @@
 package org.opendaylight.mdsal.binding.generator.impl;
 
 import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.Beta;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.MoreObjects.ToStringHelper;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.util.concurrent.Futures;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.MultimapBuilder;
 import com.google.common.util.concurrent.ListenableFuture;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-import java.lang.ref.WeakReference;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Optional;
 import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
+import org.checkerframework.checker.lock.qual.GuardedBy;
+import org.checkerframework.checker.lock.qual.Holding;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.binding.generator.api.BindingRuntimeContext;
+import org.opendaylight.mdsal.binding.generator.api.BindingRuntimeGenerator;
 import org.opendaylight.mdsal.binding.generator.api.ClassLoadingStrategy;
 import org.opendaylight.mdsal.binding.generator.api.ModuleInfoRegistry;
 import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
@@ -30,19 +42,73 @@ import org.opendaylight.yangtools.concepts.ObjectRegistration;
 import org.opendaylight.yangtools.util.ClassLoaderUtils;
 import org.opendaylight.yangtools.yang.binding.YangModuleInfo;
 import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaContextProvider;
+import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException;
 import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException;
 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceProvider;
 import org.opendaylight.yangtools.yang.parser.repo.YangTextSchemaContextResolver;
+import org.opendaylight.yangtools.yang.parser.repo.YangTextSchemaSourceRegistration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 public final class ModuleInfoBackedContext extends GeneratedClassLoadingStrategy
         implements ModuleInfoRegistry, SchemaContextProvider, SchemaSourceProvider<YangTextSchemaSource> {
+    private abstract static class AbstractRegisteredModuleInfo {
+        final YangTextSchemaSourceRegistration reg;
+        final YangModuleInfo info;
+        final ClassLoader loader;
+
+        AbstractRegisteredModuleInfo(final YangModuleInfo info, final YangTextSchemaSourceRegistration reg,
+            final ClassLoader loader) {
+            this.info = requireNonNull(info);
+            this.reg = requireNonNull(reg);
+            this.loader = requireNonNull(loader);
+        }
+
+        @Override
+        public final String toString() {
+            return addToStringAttributes(MoreObjects.toStringHelper(this)).toString();
+        }
+
+        ToStringHelper addToStringAttributes(final ToStringHelper helper) {
+            return helper.add("info", info).add("registration", reg).add("classLoader", loader);
+        }
+    }
+
+    private static final class ExplicitRegisteredModuleInfo extends AbstractRegisteredModuleInfo {
+        private int refcount = 1;
+
+        ExplicitRegisteredModuleInfo(final YangModuleInfo info, final YangTextSchemaSourceRegistration reg,
+                final ClassLoader loader) {
+            super(info, reg, loader);
+        }
+
+        void incRef() {
+            ++refcount;
+        }
+
+        boolean decRef() {
+            return --refcount == 0;
+        }
+
+        @Override
+        ToStringHelper addToStringAttributes(final ToStringHelper helper) {
+            return super.addToStringAttributes(helper).add("refCount", refcount);
+        }
+    }
+
+    private static final class ImplicitRegisteredModuleInfo extends AbstractRegisteredModuleInfo {
+        ImplicitRegisteredModuleInfo(final YangModuleInfo info, final YangTextSchemaSourceRegistration reg,
+                final ClassLoader loader) {
+            super(info, reg, loader);
+        }
+    }
+
     private static final Logger LOG = LoggerFactory.getLogger(ModuleInfoBackedContext.class);
 
     private static final LoadingCache<ClassLoadingStrategy,
@@ -65,10 +131,14 @@ public final class ModuleInfoBackedContext extends GeneratedClassLoadingStrategy
             });
 
     private final YangTextSchemaContextResolver ctxResolver = YangTextSchemaContextResolver.create("binding-context");
-    private final ConcurrentMap<String, WeakReference<ClassLoader>> packageNameToClassLoader =
-            new ConcurrentHashMap<>();
-    private final ConcurrentMap<SourceIdentifier, YangModuleInfo> sourceIdentifierToModuleInfo =
-            new ConcurrentHashMap<>();
+
+    @GuardedBy("this")
+    private final ListMultimap<String, AbstractRegisteredModuleInfo> packageToInfoReg =
+            MultimapBuilder.hashKeys().arrayListValues().build();
+    @GuardedBy("this")
+    private final ListMultimap<SourceIdentifier, AbstractRegisteredModuleInfo> sourceToInfoReg =
+            MultimapBuilder.hashKeys().arrayListValues().build();
+
     private final ClassLoadingStrategy backingLoadingStrategy;
 
     private ModuleInfoBackedContext(final ClassLoadingStrategy loadingStrategy) {
@@ -91,111 +161,201 @@ public final class ModuleInfoBackedContext extends GeneratedClassLoadingStrategy
 
     @Override
     public SchemaContext getSchemaContext() {
-        final Optional<SchemaContext> contextOptional = tryToCreateSchemaContext();
+        final Optional<? extends SchemaContext> contextOptional = tryToCreateSchemaContext();
         checkState(contextOptional.isPresent(), "Unable to recreate SchemaContext, error while parsing");
         return contextOptional.get();
     }
 
     @Override
+    @SuppressWarnings("checkstyle:illegalCatch")
     public Class<?> loadClass(final String fullyQualifiedName) throws ClassNotFoundException {
+        // This performs an explicit check for binding classes
         final String modulePackageName = BindingReflections.getModelRootPackageName(fullyQualifiedName);
-        final WeakReference<ClassLoader> classLoaderRef = packageNameToClassLoader.get(modulePackageName);
-        if (classLoaderRef != null) {
-            final ClassLoader classLoader = classLoaderRef.get();
-            if (classLoader != null) {
-                return ClassLoaderUtils.loadClass(classLoader, fullyQualifiedName);
+
+        synchronized (this) {
+            // Try to find a loaded class loader
+            // FIXME: two-step process, try explicit registrations first
+            for (AbstractRegisteredModuleInfo reg : packageToInfoReg.get(modulePackageName)) {
+                return ClassLoaderUtils.loadClass(reg.loader, fullyQualifiedName);
             }
-        }
 
-        if (backingLoadingStrategy == null) {
-            throw new ClassNotFoundException(fullyQualifiedName);
-        }
+            // We have not found a matching registration, consult the backing strategy
+            if (backingLoadingStrategy == null) {
+                throw new ClassNotFoundException(fullyQualifiedName);
+            }
 
-        final Class<?> cls = backingLoadingStrategy.loadClass(fullyQualifiedName);
-        if (BindingReflections.isBindingClass(cls)) {
-            resolveModuleInfo(cls);
-        }
+            final Class<?> cls = backingLoadingStrategy.loadClass(fullyQualifiedName);
+            final YangModuleInfo moduleInfo;
+            try {
+                moduleInfo = BindingReflections.getModuleInfo(cls);
+            } catch (Exception e) {
+                throw new IllegalStateException("Failed to resolve module information for class " + cls, e);
+            }
 
-        return cls;
+            registerImplicitModuleInfo(requireNonNull(moduleInfo));
+            return cls;
+        }
     }
 
     @Override
-    public ObjectRegistration<YangModuleInfo> registerModuleInfo(final YangModuleInfo yangModuleInfo) {
-        YangModuleInfoRegistration registration = new YangModuleInfoRegistration(yangModuleInfo, this);
-        resolveModuleInfo(yangModuleInfo);
-        return registration;
+    public synchronized ObjectRegistration<YangModuleInfo> registerModuleInfo(final YangModuleInfo yangModuleInfo) {
+        return register(requireNonNull(yangModuleInfo));
     }
 
     @Override
-    public ListenableFuture<? extends YangTextSchemaSource> getSource(
-        final SourceIdentifier sourceIdentifier) {
-        final YangModuleInfo yangModuleInfo = sourceIdentifierToModuleInfo.get(sourceIdentifier);
-
-        if (yangModuleInfo == null) {
-            LOG.debug("Unknown schema source requested: {}, available sources: {}", sourceIdentifier,
-                sourceIdentifierToModuleInfo.keySet());
-            return Futures.immediateFailedFuture(new SchemaSourceException(
-                "Unknown schema source: " + sourceIdentifier));
-        }
-
-        return Futures.immediateFuture(YangTextSchemaSource.delegateForByteSource(sourceIdentifier,
-            yangModuleInfo.getYangTextByteSource()));
+    public ListenableFuture<? extends YangTextSchemaSource> getSource(final SourceIdentifier sourceIdentifier) {
+        return ctxResolver.getSource(sourceIdentifier);
     }
 
-    public void addModuleInfos(final Iterable<? extends YangModuleInfo> moduleInfos) {
+    public synchronized void addModuleInfos(final Iterable<? extends YangModuleInfo> moduleInfos) {
         for (YangModuleInfo yangModuleInfo : moduleInfos) {
-            registerModuleInfo(yangModuleInfo);
+            register(requireNonNull(yangModuleInfo));
         }
     }
 
+    @Beta
+    public @NonNull BindingRuntimeContext createRuntimeContext(final BindingRuntimeGenerator generator) {
+        return BindingRuntimeContext.create(generator.generateTypeMapping(tryToCreateModelContext().orElseThrow()),
+            this);
+    }
+
     // TODO finish schema parsing and expose as SchemaService
     // Unite with current SchemaService
-    // Implement remove ModuleInfo to update SchemaContext
 
-    public Optional<SchemaContext> tryToCreateSchemaContext() {
+    @Deprecated
+    public Optional<? extends SchemaContext> tryToCreateSchemaContext() {
         return ctxResolver.getSchemaContext();
     }
 
-    @SuppressWarnings("checkstyle:illegalCatch")
-    private void resolveModuleInfo(final Class<?> cls) {
-        final YangModuleInfo moduleInfo;
-        try {
-            moduleInfo = BindingReflections.getModuleInfo(cls);
-        } catch (Exception e) {
-            throw new IllegalStateException("Failed to resolve module information for class " + cls, e);
+    public Optional<? extends EffectiveModelContext> tryToCreateModelContext() {
+        return ctxResolver.getEffectiveModelContext();
+    }
+
+    @Holding("this")
+    private ObjectRegistration<YangModuleInfo> register(final @NonNull YangModuleInfo moduleInfo) {
+        final Builder<ExplicitRegisteredModuleInfo> regBuilder = ImmutableList.builder();
+        for (YangModuleInfo info : flatDependencies(moduleInfo)) {
+            regBuilder.add(registerExplicitModuleInfo(info));
         }
+        final ImmutableList<ExplicitRegisteredModuleInfo> regInfos = regBuilder.build();
+
+        return new AbstractObjectRegistration<>(moduleInfo) {
+            @Override
+            protected void removeRegistration() {
+                unregister(regInfos);
+            }
+        };
+    }
 
-        resolveModuleInfo(moduleInfo);
+    /*
+     * Perform implicit registration of a YangModuleInfo and any of its dependencies. If there is a registration for
+     * a particular source, we do not create a duplicate registration.
+     */
+    @Holding("this")
+    private void registerImplicitModuleInfo(final @NonNull YangModuleInfo moduleInfo) {
+        for (YangModuleInfo info : flatDependencies(moduleInfo)) {
+            final Class<?> infoClass = info.getClass();
+            final SourceIdentifier sourceId = sourceIdentifierFrom(info);
+            if (sourceToInfoReg.containsKey(sourceId)) {
+                LOG.debug("Skipping implicit registration of {} as source {} is already registered", info, sourceId);
+                continue;
+            }
+
+            final YangTextSchemaSourceRegistration reg;
+            try {
+                reg = ctxResolver.registerSource(toYangTextSource(sourceId, info));
+            } catch (YangSyntaxErrorException | SchemaSourceException | IOException e) {
+                LOG.warn("Failed to register info {} source {}, ignoring it", info, sourceId, e);
+                continue;
+            }
+
+            final ImplicitRegisteredModuleInfo regInfo = new ImplicitRegisteredModuleInfo(info, reg,
+                infoClass.getClassLoader());
+            sourceToInfoReg.put(sourceId, regInfo);
+            packageToInfoReg.put(BindingReflections.getModelRootPackageName(infoClass.getPackage()), regInfo);
+        }
     }
 
-    @SuppressWarnings("checkstyle:illegalCatch")
-    @SuppressFBWarnings("REC_CATCH_EXCEPTION")
-    private boolean resolveModuleInfo(final YangModuleInfo moduleInfo) {
-        final SourceIdentifier identifier = sourceIdentifierFrom(moduleInfo);
-        final YangModuleInfo previous = sourceIdentifierToModuleInfo.putIfAbsent(identifier, moduleInfo);
-        if (previous != null) {
-            return false;
+    /*
+     * Perform explicit registration of a YangModuleInfo. This always results in a new explicit registration. In case
+     * there is a pre-existing implicit registration, it is removed just after the explicit registration is made.
+     */
+    @Holding("this")
+    private ExplicitRegisteredModuleInfo registerExplicitModuleInfo(final @NonNull YangModuleInfo info) {
+        // First search for an existing explicit registration
+        final SourceIdentifier sourceId = sourceIdentifierFrom(info);
+        for (AbstractRegisteredModuleInfo reg : sourceToInfoReg.get(sourceId)) {
+            if (reg instanceof ExplicitRegisteredModuleInfo && info.equals(reg.info)) {
+                final ExplicitRegisteredModuleInfo explicit = (ExplicitRegisteredModuleInfo) reg;
+                explicit.incRef();
+                LOG.debug("Reusing explicit registration {}", explicit);
+                return explicit;
+            }
         }
 
-        ClassLoader moduleClassLoader = moduleInfo.getClass().getClassLoader();
+        // Create an explicit registration
+        final YangTextSchemaSourceRegistration reg;
         try {
-            String modulePackageName = moduleInfo.getClass().getPackage().getName();
-            packageNameToClassLoader.putIfAbsent(modulePackageName, new WeakReference<>(moduleClassLoader));
-            ctxResolver.registerSource(toYangTextSource(identifier, moduleInfo));
-            for (YangModuleInfo importedInfo : moduleInfo.getImportedModules()) {
-                resolveModuleInfo(importedInfo);
+            reg = ctxResolver.registerSource(toYangTextSource(sourceId, info));
+        } catch (YangSyntaxErrorException | SchemaSourceException | IOException e) {
+            throw new IllegalStateException("Failed to register info " + info, e);
+        }
+
+        final Class<?> infoClass = info.getClass();
+        final String packageName = BindingReflections.getModelRootPackageName(infoClass.getPackage());
+        final ExplicitRegisteredModuleInfo regInfo = new ExplicitRegisteredModuleInfo(info, reg,
+            infoClass.getClassLoader());
+        LOG.debug("Created new explicit registration {}", regInfo);
+
+        sourceToInfoReg.put(sourceId, regInfo);
+        removeImplicit(sourceToInfoReg.get(sourceId));
+        packageToInfoReg.put(packageName, regInfo);
+        removeImplicit(packageToInfoReg.get(packageName));
+
+        return regInfo;
+    }
+
+    synchronized void unregister(final ImmutableList<ExplicitRegisteredModuleInfo> regInfos) {
+        for (ExplicitRegisteredModuleInfo regInfo : regInfos) {
+            if (!regInfo.decRef()) {
+                LOG.debug("Registration {} has references, not removing it", regInfo);
+                continue;
+            }
+
+            final SourceIdentifier sourceId = sourceIdentifierFrom(regInfo.info);
+            if (!sourceToInfoReg.remove(sourceId, regInfo)) {
+                LOG.warn("Failed to find {} registered under {}", regInfo, sourceId);
             }
-        } catch (Exception e) {
-            LOG.error("Not including {} in YANG sources because of error.", moduleInfo, e);
+
+            final String packageName = BindingReflections.getModelRootPackageName(regInfo.info.getClass().getPackage());
+            if (!packageToInfoReg.remove(packageName, regInfo)) {
+                LOG.warn("Failed to find {} registered under {}", regInfo, packageName);
+            }
+
+            regInfo.reg.close();
         }
-        return true;
     }
 
-    private void remove(final YangModuleInfoRegistration registration) {
-        // FIXME implement
+    @Holding("this")
+    private static void removeImplicit(final List<AbstractRegisteredModuleInfo> regs) {
+        /*
+         * Search for implicit registration for a sourceId/packageName.
+         *
+         * Since we are called while an explicit registration is being created (and has already been inserted, we know
+         * there is at least one entry in the maps. We also know registrations retain the order in which they were
+         * created and that implicit registrations are not created if there already is a registration.
+         *
+         * This means that if an implicit registration exists, it will be the first entry in the list.
+         */
+        final AbstractRegisteredModuleInfo reg = regs.get(0);
+        if (reg instanceof ImplicitRegisteredModuleInfo) {
+            LOG.debug("Removing implicit registration {}", reg);
+            regs.remove(0);
+            reg.reg.close();
+        }
     }
 
-    private static YangTextSchemaSource toYangTextSource(final SourceIdentifier identifier,
+    private static @NonNull YangTextSchemaSource toYangTextSource(final SourceIdentifier identifier,
             final YangModuleInfo moduleInfo) {
         return YangTextSchemaSource.delegateForByteSource(identifier, moduleInfo.getYangTextByteSource());
     }
@@ -205,17 +365,23 @@ public final class ModuleInfoBackedContext extends GeneratedClassLoadingStrategy
         return RevisionSourceIdentifier.create(name.getLocalName(), name.getRevision());
     }
 
-    private static class YangModuleInfoRegistration extends AbstractObjectRegistration<YangModuleInfo> {
-        private final ModuleInfoBackedContext context;
+    private static List<YangModuleInfo> flatDependencies(final YangModuleInfo moduleInfo) {
+        // Flatten the modules being registered, with the triggering module being first...
+        final Set<YangModuleInfo> requiredInfos = new LinkedHashSet<>();
+        flatDependencies(requiredInfos, moduleInfo);
 
-        YangModuleInfoRegistration(final YangModuleInfo instance, final ModuleInfoBackedContext context) {
-            super(instance);
-            this.context = context;
-        }
+        // ... now reverse the order in an effort to register dependencies first (triggering module last)
+        final List<YangModuleInfo> intendedOrder = new ArrayList<>(requiredInfos);
+        Collections.reverse(intendedOrder);
 
-        @Override
-        protected void removeRegistration() {
-            context.remove(this);
+        return intendedOrder;
+    }
+
+    private static void flatDependencies(final Set<YangModuleInfo> set, final YangModuleInfo moduleInfo) {
+        if (set.add(moduleInfo)) {
+            for (YangModuleInfo dep : moduleInfo.getImportedModules()) {
+                flatDependencies(set, dep);
+            }
         }
     }
 }