BUG-4456: add RecursiveExtensionResolver
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / stmt / reactor / BuildGlobalContext.java
index 90b41b99120af501050a5b1a8d2ec0b43f46f8d9..dfed4282952829a51b0caa8743782e7d6d6133fd 100644 (file)
@@ -7,25 +7,27 @@
  */
 package org.opendaylight.yangtools.yang.parser.stmt.reactor;
 
-import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.effective.EffectiveSchemaContext;
-
-import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Throwables;
+import com.google.common.base.Verify;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Set;
 import javax.annotation.Nonnull;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
+import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
 import org.opendaylight.yangtools.yang.model.api.meta.IdentifierNamespace;
+import org.opendaylight.yangtools.yang.parser.spi.meta.DerivedNamespaceBehaviour;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase;
 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour;
 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.NamespaceStorageNode;
@@ -37,9 +39,16 @@ 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.source.SourceException;
 import org.opendaylight.yangtools.yang.parser.spi.source.StatementStreamSource;
+import org.opendaylight.yangtools.yang.parser.spi.validation.ValidationBundlesNamespace;
+import org.opendaylight.yangtools.yang.parser.spi.validation.ValidationBundlesNamespace.ValidationBundleType;
 import org.opendaylight.yangtools.yang.parser.stmt.reactor.SourceSpecificContext.PhaseCompletionProgress;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.RecursiveObjectLeaker;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.effective.EffectiveSchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 class BuildGlobalContext extends NamespaceStorageSupport implements NamespaceBehaviour.Registry {
+    private static final Logger LOG = LoggerFactory.getLogger(BuildGlobalContext.class);
 
     private static final List<ModelProcessingPhase> PHASE_EXECUTION_ORDER = ImmutableList.<ModelProcessingPhase>builder()
             .add(ModelProcessingPhase.SOURCE_LINKAGE)
@@ -49,25 +58,33 @@ class BuildGlobalContext extends NamespaceStorageSupport implements NamespaceBeh
             .build();
 
     private final Map<QName,StatementDefinitionContext<?,?,?>> definitions = new HashMap<>();
-    private final Map<Class<?>,NamespaceBehaviourWithListeners<?, ?, ?>> namespaces = new HashMap<>();
-
+    private final Map<Class<?>,NamespaceBehaviourWithListeners<?, ?, ?>> supportedNamespaces = new HashMap<>();
 
     private final Map<ModelProcessingPhase,StatementSupportBundle> supports;
     private final Set<SourceSpecificContext> sources = new HashSet<>();
 
-    private ModelProcessingPhase currentPhase;
-    private ModelProcessingPhase finishedPhase;
+    private ModelProcessingPhase currentPhase = ModelProcessingPhase.INIT;
+    private ModelProcessingPhase finishedPhase = ModelProcessingPhase.INIT;
 
-    public BuildGlobalContext(Map<ModelProcessingPhase, StatementSupportBundle> supports) {
+    public BuildGlobalContext(final Map<ModelProcessingPhase, StatementSupportBundle> supports) {
         super();
-        this.supports = supports;
+        this.supports = Preconditions.checkNotNull(supports, "BuildGlobalContext#supports cannot be null");
     }
 
-    public StatementSupportBundle getSupportsForPhase(ModelProcessingPhase currentPhase) {
+    public BuildGlobalContext(final Map<ModelProcessingPhase, StatementSupportBundle> supports, final Map<ValidationBundleType,Collection<?>> supportedValidation) {
+        super();
+        this.supports = Preconditions.checkNotNull(supports, "BuildGlobalContext#supports cannot be null");
+
+        for (Entry<ValidationBundleType, Collection<?>> validationBundle : supportedValidation.entrySet()) {
+            addToNs(ValidationBundlesNamespace.class, validationBundle.getKey(), validationBundle.getValue());
+        }
+    }
+
+    public StatementSupportBundle getSupportsForPhase(final ModelProcessingPhase currentPhase) {
         return supports.get(currentPhase);
     }
 
-    public void addSource(@Nonnull StatementStreamSource source) {
+    public void addSource(@Nonnull final StatementStreamSource source) {
         sources.add(new SourceSpecificContext(this,source));
     }
 
@@ -87,32 +104,45 @@ class BuildGlobalContext extends NamespaceStorageSupport implements NamespaceBeh
     }
 
     @Override
-    public <K, V, N extends IdentifierNamespace<K, V>> NamespaceBehaviourWithListeners<K, V, N> getNamespaceBehaviour(Class<N> type) {
-        NamespaceBehaviourWithListeners<?, ?, ?> potential = namespaces.get(type);
+    public <K, V, N extends IdentifierNamespace<K, V>> NamespaceBehaviourWithListeners<K, V, N> getNamespaceBehaviour(final Class<N> type) {
+        NamespaceBehaviourWithListeners<?, ?, ?> potential = supportedNamespaces.get(type);
         if (potential == null) {
             NamespaceBehaviour<K, V, N> potentialRaw = supports.get(currentPhase).getNamespaceBehaviour(type);
-            if(potentialRaw != null) {
-                potential = new NamespaceBehaviourWithListeners<>(potentialRaw);
-                namespaces.put(type, potential);
+            if (potentialRaw != null) {
+                potential = createNamespaceContext(potentialRaw);
+                supportedNamespaces.put(type, potential);
+            } else {
+                throw new NamespaceNotAvailableException(
+                        "Namespace " + type + " is not available in phase " + currentPhase);
             }
         }
-        if (potential != null) {
-            Preconditions.checkState(type.equals(potential.getIdentifier()));
-
-            /*
-             * Safe cast, previous checkState checks equivalence of key from
-             * which type argument are derived
-             */
-            return (NamespaceBehaviourWithListeners<K, V, N>) potential;
+
+        Verify.verify(type.equals(potential.getIdentifier()));
+        /*
+         * Safe cast, previous checkState checks equivalence of key from which type argument are
+         * derived
+         */
+        return (NamespaceBehaviourWithListeners<K, V, N>) potential;
+    }
+
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    private <K, V, N extends IdentifierNamespace<K, V>> NamespaceBehaviourWithListeners<K, V, N> createNamespaceContext(
+            final NamespaceBehaviour<K, V, N> potentialRaw) {
+        if (potentialRaw instanceof DerivedNamespaceBehaviour) {
+            VirtualNamespaceContext derivedContext =
+                    new VirtualNamespaceContext((DerivedNamespaceBehaviour) potentialRaw);
+            getNamespaceBehaviour(((DerivedNamespaceBehaviour) potentialRaw).getDerivedFrom())
+                    .addDerivedNamespace(derivedContext);
+            return derivedContext;
         }
-        throw new NamespaceNotAvailableException("Namespace " + type + " is not available in phase " + currentPhase);
+        return new SimpleNamespaceContext<>(potentialRaw);
     }
 
-    public StatementDefinitionContext<?, ?, ?> getStatementDefinition(QName name) {
+    public StatementDefinitionContext<?, ?, ?> getStatementDefinition(final QName name) {
         StatementDefinitionContext<?, ?, ?> potential = definitions.get(name);
-        if(potential == null) {
+        if (potential == null) {
             StatementSupport<?, ?, ?> potentialRaw = supports.get(currentPhase).getStatementDefinition(name);
-            if(potentialRaw != null) {
+            if (potentialRaw != null) {
                 potential = new StatementDefinitionContext<>(potentialRaw);
                 definitions.put(name, potential);
             }
@@ -121,7 +151,7 @@ class BuildGlobalContext extends NamespaceStorageSupport implements NamespaceBeh
     }
 
     public EffectiveModelContext build() throws SourceException, ReactorException {
-        for(ModelProcessingPhase phase : PHASE_EXECUTION_ORDER) {
+        for (ModelProcessingPhase phase : PHASE_EXECUTION_ORDER) {
             startPhase(phase);
             loadPhaseStatements();
             completePhaseActions();
@@ -132,16 +162,15 @@ class BuildGlobalContext extends NamespaceStorageSupport implements NamespaceBeh
 
     private EffectiveModelContext transform() {
         Preconditions.checkState(finishedPhase == ModelProcessingPhase.EFFECTIVE_MODEL);
-        List<DeclaredStatement<?>> rootStatements = new ArrayList<>();
-        for(SourceSpecificContext source : sources) {
-            DeclaredStatement<?> root = source.getRoot().buildDeclared();
-            rootStatements.add(root);
+        List<DeclaredStatement<?>> rootStatements = new ArrayList<>(sources.size());
+        for (SourceSpecificContext source : sources) {
+            rootStatements.add(source.getRoot().buildDeclared());
         }
         return new EffectiveModelContext(rootStatements);
     }
 
     public EffectiveSchemaContext buildEffective() throws SourceException, ReactorException {
-        for(ModelProcessingPhase phase : PHASE_EXECUTION_ORDER) {
+        for (ModelProcessingPhase phase : PHASE_EXECUTION_ORDER) {
             startPhase(phase);
             loadPhaseStatements();
             completePhaseActions();
@@ -152,23 +181,25 @@ class BuildGlobalContext extends NamespaceStorageSupport implements NamespaceBeh
 
     private EffectiveSchemaContext transformEffective() {
         Preconditions.checkState(finishedPhase == ModelProcessingPhase.EFFECTIVE_MODEL);
-        List<DeclaredStatement<?>> rootStatements = new ArrayList<>();
-        List<EffectiveStatement<?,?>> rootEffectiveStatements = new ArrayList<>();
+        List<DeclaredStatement<?>> rootStatements = new ArrayList<>(sources.size());
+        List<EffectiveStatement<?,?>> rootEffectiveStatements = new ArrayList<>(sources.size());
 
-        for(SourceSpecificContext source : sources) {
-            DeclaredStatement<?> root = source.getRoot().buildDeclared();
-            rootStatements.add(root);
-
-            EffectiveStatement<?,?> rootEffective = source.getRoot().buildEffective();
-            rootEffectiveStatements.add(rootEffective);
+        try {
+            for (SourceSpecificContext source : sources) {
+                final RootStatementContext<?, ?, ?> root = source.getRoot();
+                rootStatements.add(root.buildDeclared());
+                rootEffectiveStatements.add(root.buildEffective());
+            }
+        } finally {
+            RecursiveObjectLeaker.cleanup();
         }
 
-        return new EffectiveSchemaContext(rootStatements,rootEffectiveStatements);
+        return new EffectiveSchemaContext(rootStatements, rootEffectiveStatements);
     }
 
-    private void startPhase(ModelProcessingPhase phase) {
+    private void startPhase(final ModelProcessingPhase phase) {
         Preconditions.checkState(Objects.equals(finishedPhase, phase.getPreviousPhase()));
-        for(SourceSpecificContext source : sources) {
+        for (SourceSpecificContext source : sources) {
             source.startPhase(phase);
         }
         currentPhase = phase;
@@ -176,22 +207,64 @@ class BuildGlobalContext extends NamespaceStorageSupport implements NamespaceBeh
 
     private  void loadPhaseStatements() throws SourceException {
         Preconditions.checkState(currentPhase != null);
-        for(SourceSpecificContext source : sources) {
+        for (SourceSpecificContext source : sources) {
             source.loadStatements();
         }
     }
 
+    private SomeModifiersUnresolvedException addSourceExceptions(final SomeModifiersUnresolvedException buildFailure,
+            final List<SourceSpecificContext> sourcesToProgress) {
+        boolean addedCause = false;
+        for (SourceSpecificContext failedSource : sourcesToProgress) {
+            final SourceException sourceEx = failedSource.failModifiers(currentPhase);
+
+            // Workaround for broken logging implementations which ignore suppressed exceptions
+            Throwable cause = sourceEx.getCause() != null ? sourceEx.getCause() : sourceEx;
+            if (LOG.isDebugEnabled()) {
+                LOG.error("Failed to parse YANG from source {}", failedSource, sourceEx);
+            } else {
+                LOG.error("Failed to parse YANG from source {}: {}", failedSource, cause.getMessage());
+            }
+
+            final Throwable[] suppressed = sourceEx.getSuppressed();
+            if (suppressed.length > 0) {
+                LOG.error("{} additional errors reported:", suppressed.length);
+
+                int i = 1;
+                for (Throwable t : suppressed) {
+                    // FIXME: this should be configured in the appender, really
+                    if (LOG.isDebugEnabled()) {
+                        LOG.error("Error {}: {}", i, t.getMessage(), t);
+                    } else {
+                        LOG.error("Error {}: {}", i, t.getMessage());
+                    }
+
+                    i++;
+                }
+            }
+
+            if (!addedCause) {
+                addedCause = true;
+                buildFailure.initCause(sourceEx);
+            } else {
+                buildFailure.addSuppressed(sourceEx);
+            }
+        }
+        return buildFailure;
+    }
+
     private  void completePhaseActions() throws ReactorException {
         Preconditions.checkState(currentPhase != null);
         List<SourceSpecificContext> sourcesToProgress = Lists.newArrayList(sources);
         try {
             boolean progressing = true;
-            while(progressing) {
+            while (progressing) {
                 // We reset progressing to false.
                 progressing = false;
                 Iterator<SourceSpecificContext> currentSource = sourcesToProgress.iterator();
                 while(currentSource.hasNext()) {
-                    PhaseCompletionProgress sourceProgress = currentSource.next().tryToCompletePhase(currentPhase);
+                    SourceSpecificContext nextSourceCtx = currentSource.next();
+                    PhaseCompletionProgress sourceProgress = nextSourceCtx.tryToCompletePhase(currentPhase);
                     switch (sourceProgress) {
                         case FINISHED:
                             currentSource.remove();
@@ -210,17 +283,14 @@ class BuildGlobalContext extends NamespaceStorageSupport implements NamespaceBeh
         } catch (SourceException e) {
             throw Throwables.propagate(e);
         }
-        if(!sourcesToProgress.isEmpty()) {
+        if (!sourcesToProgress.isEmpty()) {
             SomeModifiersUnresolvedException buildFailure = new SomeModifiersUnresolvedException(currentPhase);
-                for(SourceSpecificContext failedSource : sourcesToProgress) {
-                    SourceException sourceEx = failedSource.failModifiers(currentPhase);
-                    buildFailure.addSuppressed(sourceEx);
-                }
-                throw buildFailure;
+            buildFailure = addSourceExceptions(buildFailure, sourcesToProgress);
+            throw buildFailure;
         }
     }
 
-    private  void endPhase(ModelProcessingPhase phase) {
+    private  void endPhase(final ModelProcessingPhase phase) {
         Preconditions.checkState(currentPhase == phase);
         finishedPhase = currentPhase;
     }
@@ -228,5 +298,4 @@ class BuildGlobalContext extends NamespaceStorageSupport implements NamespaceBeh
     public Set<SourceSpecificContext> getSources() {
         return sources;
     }
-
 }