StatementSupport is not a StatementDefinition
[yangtools.git] / parser / yang-parser-reactor / src / main / java / org / opendaylight / yangtools / yang / parser / stmt / reactor / SourceSpecificContext.java
index 79d8e3af5422567f96e75d80d82375a95b86ebcb..4626f24fc10b44a25a643b4f2526c4f5e8e88668 100644 (file)
@@ -20,6 +20,7 @@ import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Optional;
 import org.eclipse.jdt.annotation.NonNull;
@@ -39,6 +40,7 @@ import org.opendaylight.yangtools.yang.parser.spi.meta.ParserNamespace;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StatementDefinitionNamespace;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StatementSupport;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StatementSupportBundle;
+import org.opendaylight.yangtools.yang.parser.spi.meta.StatementSupportNamespace;
 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
 import org.opendaylight.yangtools.yang.parser.spi.source.BelongsToModuleContext;
 import org.opendaylight.yangtools.yang.parser.spi.source.BelongsToPrefixToModuleCtx;
@@ -63,11 +65,41 @@ final class SourceSpecificContext implements NamespaceStorageNode, NamespaceBeha
         FINISHED
     }
 
+    private static final class SupportedStatements
+            extends NamespaceBehaviour<QName, StatementSupport<?, ?, ?>, StatementSupportNamespace> {
+        private final QNameToStatementDefinitionMap statementDefinitions;
+
+        SupportedStatements(final QNameToStatementDefinitionMap statementDefinitions) {
+            super(StatementSupportNamespace.class);
+            this.statementDefinitions = requireNonNull(statementDefinitions);
+        }
+
+        @Override
+        public StatementSupport<?, ?, ?> getFrom(final NamespaceStorageNode storage, final QName key) {
+            return statementDefinitions.getSupport(key);
+        }
+
+        @Override
+        public Map<QName, StatementSupport<?, ?, ?>> getAllFrom(final NamespaceStorageNode storage) {
+            throw uoe();
+        }
+
+        @Override
+        public void addTo(final NamespaceStorageNode storage, final QName key, final StatementSupport<?, ?, ?> value) {
+            throw uoe();
+        }
+
+        private static UnsupportedOperationException uoe() {
+            return new UnsupportedOperationException("StatementSupportNamespace is immutable");
+        }
+    }
+
     private static final Logger LOG = LoggerFactory.getLogger(SourceSpecificContext.class);
 
     // TODO: consider keying by Byte equivalent of ExecutionOrder
     private final Multimap<ModelProcessingPhase, ModifierImpl> modifiers = HashMultimap.create();
     private final QNameToStatementDefinitionMap qnameToStmtDefMap = new QNameToStatementDefinitionMap();
+    private final SupportedStatements statementSupports = new SupportedStatements(qnameToStmtDefMap);
     private final PrefixToModuleMap prefixToModuleMap = new PrefixToModuleMap();
     private final @NonNull BuildGlobalContext globalContext;
 
@@ -85,6 +117,9 @@ final class SourceSpecificContext implements NamespaceStorageNode, NamespaceBeha
     private ModelProcessingPhase finishedPhase = ModelProcessingPhase.INIT;
     private ModelProcessingPhase inProgressPhase;
 
+    // If not null, do not add anything to modifiers, but record it here.
+    private List<Entry<ModelProcessingPhase, ModifierImpl>> delayedModifiers;
+
     SourceSpecificContext(final BuildGlobalContext globalContext, final StatementStreamSource source) {
         this.globalContext = requireNonNull(globalContext);
         this.source = requireNonNull(source);
@@ -104,7 +139,7 @@ final class SourceSpecificContext implements NamespaceStorageNode, NamespaceBeha
         if (def == null) {
             def = globalContext.getModelDefinedStatementDefinition(name);
             if (def == null) {
-                final StatementSupport<?, ?, ?> extension = qnameToStmtDefMap.get(name);
+                final StatementSupport<?, ?, ?> extension = qnameToStmtDefMap.getSupport(name);
                 if (extension != null) {
                     def = new StatementDefinitionContext<>(extension);
                     globalContext.putModelDefinedStatementDefinition(name, def);
@@ -253,8 +288,12 @@ final class SourceSpecificContext implements NamespaceStorageNode, NamespaceBeha
     }
 
     @Override
+    @SuppressWarnings("unchecked")
     public <K, V, N extends ParserNamespace<K, V>> NamespaceBehaviour<K, V, N> getNamespaceBehaviour(
             final Class<N> type) {
+        if (StatementSupportNamespace.class.equals(type)) {
+            return (NamespaceBehaviour<K, V, N>) statementSupports;
+        }
         return globalContext.getNamespaceBehaviour(type);
     }
 
@@ -288,9 +327,13 @@ final class SourceSpecificContext implements NamespaceStorageNode, NamespaceBeha
         return hasProgressed ? PhaseCompletionProgress.PROGRESS : PhaseCompletionProgress.NO_PROGRESS;
     }
 
-    private static boolean tryToProgress(final Collection<ModifierImpl> currentPhaseModifiers) {
+    private boolean tryToProgress(final Collection<ModifierImpl> currentPhaseModifiers) {
         boolean hasProgressed = false;
 
+        // We are about to iterate over the modifiers and invoke callbacks. Those callbacks can end up circling back
+        // and modifying the same collection. This asserts that modifiers should not be modified.
+        delayedModifiers = List.of();
+
         // Try making forward progress ...
         final Iterator<ModifierImpl> modifier = currentPhaseModifiers.iterator();
         while (modifier.hasNext()) {
@@ -300,12 +343,34 @@ final class SourceSpecificContext implements NamespaceStorageNode, NamespaceBeha
             }
         }
 
+        // We have finished iterating, if we have any delayed modifiers, put them back. This may seem as if we want
+        // to retry the loop, but we do not have to, as we will be circling back anyway.
+        //
+        // The thing is, we are inherently single-threaded and therefore if we observe non-empty delayedModifiers, the
+        // only way that could happen is through a callback, which in turn means we have made progress.
+        if (!delayedModifiers.isEmpty()) {
+            verify(hasProgressed, "Delayed modifiers encountered without making progress in %s", this);
+            for (Entry<ModelProcessingPhase, ModifierImpl> entry : delayedModifiers) {
+                modifiers.put(entry.getKey(), entry.getValue());
+            }
+        }
+        delayedModifiers = null;
+
         return hasProgressed;
     }
 
     @NonNull ModelActionBuilder newInferenceAction(final @NonNull ModelProcessingPhase phase) {
         final ModifierImpl action = new ModifierImpl();
-        modifiers.put(phase, action);
+
+        if (delayedModifiers != null) {
+            if (delayedModifiers.isEmpty()) {
+                delayedModifiers = new ArrayList<>(2);
+            }
+            delayedModifiers.add(Map.entry(phase,action));
+        } else {
+            modifiers.put(phase, action);
+        }
+
         return action;
     }
 
@@ -325,15 +390,17 @@ final class SourceSpecificContext implements NamespaceStorageNode, NamespaceBeha
             }
         }
 
-        if (exceptions.isEmpty()) {
-            return Optional.empty();
+        switch (exceptions.size()) {
+            case 0:
+                return Optional.empty();
+            case 1:
+                return Optional.of(exceptions.get(0));
+            default:
+                final String message = String.format("Yang model processing phase %s failed", identifier);
+                final InferenceException ex = new InferenceException(message, root, exceptions.get(0));
+                exceptions.listIterator(1).forEachRemaining(ex::addSuppressed);
+                return Optional.of(ex);
         }
-
-        final String message = String.format("Yang model processing phase %s failed", identifier);
-        final InferenceException e = new InferenceException(message, root, exceptions.get(0));
-        exceptions.listIterator(1).forEachRemaining(e::addSuppressed);
-
-        return Optional.of(e);
     }
 
     void loadStatements() {