StatementSupport is not a StatementDefinition
[yangtools.git] / parser / yang-parser-reactor / src / main / java / org / opendaylight / yangtools / yang / parser / stmt / reactor / SourceSpecificContext.java
index 484f291a0929b75178e400bf2a7b1d820c881571..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;
@@ -75,17 +76,21 @@ final class SourceSpecificContext implements NamespaceStorageNode, NamespaceBeha
 
         @Override
         public StatementSupport<?, ?, ?> getFrom(final NamespaceStorageNode storage, final QName key) {
-            return statementDefinitions.get(key);
+            return statementDefinitions.getSupport(key);
         }
 
         @Override
         public Map<QName, StatementSupport<?, ?, ?>> getAllFrom(final NamespaceStorageNode storage) {
-            throw new UnsupportedOperationException("StatementSupportNamespace is immutable");
+            throw uoe();
         }
 
         @Override
         public void addTo(final NamespaceStorageNode storage, final QName key, final StatementSupport<?, ?, ?> value) {
-            throw new UnsupportedOperationException("StatementSupportNamespace is immutable");
+            throw uoe();
+        }
+
+        private static UnsupportedOperationException uoe() {
+            return new UnsupportedOperationException("StatementSupportNamespace is immutable");
         }
     }
 
@@ -112,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);
@@ -131,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);
@@ -319,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()) {
@@ -331,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;
     }
 
@@ -356,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() {