Add YangTextSchemaContextResolver feature support
[yangtools.git] / parser / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / repo / YangTextSchemaContextResolver.java
index 6782eb8c9b6a1e2cc362db778235a4dc2d1e0fc6..215297e71985e3bf9f59a2806593f942afff5b1d 100644 (file)
@@ -13,21 +13,32 @@ import static java.util.Objects.requireNonNull;
 import com.google.common.annotations.Beta;
 import com.google.common.base.Verify;
 import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Multimap;
 import com.google.common.util.concurrent.FluentFuture;
 import java.io.IOException;
 import java.net.URL;
 import java.time.Duration;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentLinkedDeque;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.atomic.AtomicReference;
+import org.checkerframework.checker.lock.qual.GuardedBy;
 import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.concepts.AbstractRegistration;
+import org.opendaylight.yangtools.concepts.Registration;
 import org.opendaylight.yangtools.util.concurrent.FluentFutures;
+import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.repo.api.FeatureSet;
 import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException;
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactoryConfiguration;
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository;
@@ -55,14 +66,19 @@ public final class YangTextSchemaContextResolver implements AutoCloseable, Schem
 
     private final Collection<SourceIdentifier> requiredSources = new ConcurrentLinkedDeque<>();
     private final Multimap<SourceIdentifier, YangTextSchemaSource> texts = ArrayListMultimap.create();
+    @GuardedBy("this")
+    private final Map<QNameModule, List<ImmutableSet<String>>> registeredFeatures = new HashMap<>();
     private final AtomicReference<Optional<EffectiveModelContext>> currentSchemaContext =
             new AtomicReference<>(Optional.empty());
     private final GuavaSchemaSourceCache<YangIRSchemaSource> cache;
     private final SchemaListenerRegistration transReg;
     private final SchemaSourceRegistry registry;
     private final SchemaRepository repository;
+
     private volatile Object version = new Object();
     private volatile Object contextVersion = version;
+    @GuardedBy("this")
+    private FeatureSet supportedFeatures = null;
 
     private YangTextSchemaContextResolver(final SchemaRepository repository, final SchemaSourceRegistry registry) {
         this.repository = requireNonNull(repository);
@@ -168,6 +184,60 @@ public final class YangTextSchemaContextResolver implements AutoCloseable, Schem
         return registerSource(YangTextSchemaSource.forURL(url, guessSourceIdentifier(fileName)));
     }
 
+    /**
+     * Register a {@link QNameModule} as a known module namespace with a set of supported features. Union of these
+     * registrations is forwarded to {@link FeatureSet} and this is then used in {@link #getEffectiveModelContext()} and
+     * related methods.
+     *
+     * @param module Module namespace
+     * @param features Features supported for that module namespace
+     * @return a {@link Registration}, use {@link Registration#close()} to revert the effects of this method
+     * @throws NullPointerException if any argument is {@code null}
+     */
+    public @NonNull Registration registerSupportedFeatures(final QNameModule module, final Set<String> features) {
+        final var checked = requireNonNull(module);
+        final var copy = ImmutableSet.copyOf(features);
+
+        synchronized (this) {
+            version = new Object();
+            supportedFeatures = null;
+            registeredFeatures.computeIfAbsent(module, ignored -> new ArrayList<>()).add(copy);
+        }
+        return new AbstractRegistration() {
+            @Override
+            protected void removeRegistration() {
+                removeFeatures(checked, copy);
+            }
+        };
+    }
+
+    private synchronized void removeFeatures(final QNameModule module, final ImmutableSet<String> features) {
+        final var moduleFeatures = registeredFeatures.get(module);
+        if (moduleFeatures != null && moduleFeatures.remove(features)) {
+            if (moduleFeatures.isEmpty()) {
+                registeredFeatures.remove(module);
+            }
+            supportedFeatures = null;
+            version = new Object();
+        }
+    }
+
+    private synchronized @Nullable FeatureSet getSupportedFeatures() {
+        var local = supportedFeatures;
+        if (local == null && !registeredFeatures.isEmpty()) {
+            final var builder = ImmutableMap.<QNameModule, ImmutableSet<String>>builder();
+            for (var entry : registeredFeatures.entrySet()) {
+                builder.put(entry.getKey(), entry.getValue().stream()
+                    .flatMap(Set::stream)
+                    .distinct()
+                    .sorted()
+                    .collect(ImmutableSet.toImmutableSet()));
+            }
+            supportedFeatures = local = new FeatureSet(builder.build());
+        }
+        return local;
+    }
+
     private static SourceIdentifier guessSourceIdentifier(final @NonNull String fileName) {
         try {
             return YangTextSchemaSource.identifierFromFilename(fileName);
@@ -196,7 +266,6 @@ public final class YangTextSchemaContextResolver implements AutoCloseable, Schem
      */
     public Optional<? extends EffectiveModelContext> getEffectiveModelContext(
             final StatementParserMode statementParserMode) {
-        final var factory = repository.createEffectiveModelContextFactory(config(statementParserMode));
         Optional<EffectiveModelContext> sc;
         Object ver;
         do {
@@ -217,6 +286,9 @@ public final class YangTextSchemaContextResolver implements AutoCloseable, Schem
                 sources = ImmutableSet.copyOf(requiredSources);
             } while (ver != version);
 
+            final var factory = repository.createEffectiveModelContextFactory(
+                config(statementParserMode, getSupportedFeatures()));
+
             while (true) {
                 final var f = factory.createEffectiveModelContext(sources);
                 try {
@@ -282,7 +354,8 @@ public final class YangTextSchemaContextResolver implements AutoCloseable, Schem
     @SuppressWarnings("checkstyle:avoidHidingCauseException")
     public EffectiveModelContext trySchemaContext(final StatementParserMode statementParserMode)
             throws SchemaResolutionException {
-        final var future = repository.createEffectiveModelContextFactory(config(statementParserMode))
+        final var future = repository
+                .createEffectiveModelContextFactory(config(statementParserMode, getSupportedFeatures()))
                 .createEffectiveModelContext(ImmutableSet.copyOf(requiredSources));
 
         try {
@@ -303,7 +376,12 @@ public final class YangTextSchemaContextResolver implements AutoCloseable, Schem
         transReg.close();
     }
 
-    private static @NonNull SchemaContextFactoryConfiguration config(final StatementParserMode statementParserMode) {
-        return SchemaContextFactoryConfiguration.builder().setStatementParserMode(statementParserMode).build();
+    private static @NonNull SchemaContextFactoryConfiguration config(
+            final StatementParserMode statementParserMode, final @Nullable FeatureSet supportedFeatures) {
+        final var builder = SchemaContextFactoryConfiguration.builder().setStatementParserMode(statementParserMode);
+        if (supportedFeatures != null) {
+            builder.setSupportedFeatures(supportedFeatures);
+        }
+        return builder.build();
     }
 }