BUG-7052: Move qnameFromArgument to StmtContextUtils
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / stmt / reactor / BuildGlobalContext.java
index 62822474d7042f4659c9bde7afcaced862b000ef..246b71a50e2b33c492df88df7c55cc6bb4d5256f 100644 (file)
@@ -9,10 +9,16 @@ package org.opendaylight.yangtools.yang.parser.stmt.reactor;
 
 import com.google.common.base.Preconditions;
 import com.google.common.base.Verify;
+import com.google.common.collect.HashBasedTable;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Table;
+import com.google.common.collect.TreeBasedTable;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -20,10 +26,16 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
-import java.util.function.Predicate;
+import java.util.SortedMap;
 import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.util.RecursiveObjectLeaker;
 import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
+import org.opendaylight.yangtools.yang.common.YangVersion;
+import org.opendaylight.yangtools.yang.model.api.ModuleIdentifier;
 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;
@@ -31,6 +43,7 @@ import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
 import org.opendaylight.yangtools.yang.model.repo.api.StatementParserMode;
 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.MutableStatement;
 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour;
 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.NamespaceStorageNode;
 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.StorageNodeType;
@@ -39,6 +52,9 @@ import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
 import org.opendaylight.yangtools.yang.parser.spi.meta.SomeModifiersUnresolvedException;
 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.StmtContextUtils;
+import org.opendaylight.yangtools.yang.parser.spi.source.ModulesDeviatedByModules;
+import org.opendaylight.yangtools.yang.parser.spi.source.ModulesDeviatedByModules.SupportedModules;
 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.source.SupportedFeaturesNamespace;
@@ -46,8 +62,6 @@ import org.opendaylight.yangtools.yang.parser.spi.source.SupportedFeaturesNamesp
 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.Utils;
 import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.effective.EffectiveSchemaContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -60,56 +74,72 @@ class BuildGlobalContext extends NamespaceStorageSupport implements NamespaceBeh
             .add(ModelProcessingPhase.SOURCE_LINKAGE).add(ModelProcessingPhase.STATEMENT_DEFINITION)
             .add(ModelProcessingPhase.FULL_DECLARATION).add(ModelProcessingPhase.EFFECTIVE_MODEL).build();
 
-    private final Map<QName, StatementDefinitionContext<?, ?, ?>> definitions = new HashMap<>();
+    private final Table<YangVersion, QName, StatementDefinitionContext<?, ?, ?>> definitions = HashBasedTable.create();
+    private final Map<QName, StatementDefinitionContext<?, ?, ?>> modelDefinedStmtDefs = new HashMap<>();
     private final Map<Class<?>, NamespaceBehaviourWithListeners<?, ?, ?>> supportedNamespaces = new HashMap<>();
-
+    private final List<MutableStatement> mutableStatementsToSeal = new ArrayList<>();
     private final Map<ModelProcessingPhase, StatementSupportBundle> supports;
     private final Set<SourceSpecificContext> sources = new HashSet<>();
+    private final Set<YangVersion> supportedVersions;
+    private final boolean enabledSemanticVersions;
 
+    private Set<SourceSpecificContext> libSources = new HashSet<>();
     private ModelProcessingPhase currentPhase = ModelProcessingPhase.INIT;
     private ModelProcessingPhase finishedPhase = ModelProcessingPhase.INIT;
 
-    private final boolean enabledSemanticVersions;
-
-    public BuildGlobalContext(final Map<ModelProcessingPhase, StatementSupportBundle> supports,
-            final StatementParserMode statementParserMode, final Predicate<QName> isFeatureSupported) {
-        super();
-        this.supports = Preconditions.checkNotNull(supports, "BuildGlobalContext#supports cannot be null");
-        Preconditions.checkNotNull(statementParserMode, "Statement parser mode must not be null.");
-        this.enabledSemanticVersions = statementParserMode == StatementParserMode.SEMVER_MODE;
-
-        addToNs(SupportedFeaturesNamespace.class, SupportedFeatures.SUPPORTED_FEATURES,
-                Preconditions.checkNotNull(isFeatureSupported, "Supported feature predicate must not be null."));
-    }
-
-    public BuildGlobalContext(final Map<ModelProcessingPhase, StatementSupportBundle> supports,
+    BuildGlobalContext(final Map<ModelProcessingPhase, StatementSupportBundle> supports,
             final Map<ValidationBundleType, Collection<?>> supportedValidation,
-            final StatementParserMode statementParserMode, final Predicate<QName> isFeatureSupported) {
-        super();
+            final StatementParserMode statementParserMode) {
         this.supports = Preconditions.checkNotNull(supports, "BuildGlobalContext#supports cannot be null");
-        Preconditions.checkNotNull(statementParserMode, "Statement parser mode must not be null.");
-        this.enabledSemanticVersions = statementParserMode == StatementParserMode.SEMVER_MODE;
 
-        for (Entry<ValidationBundleType, Collection<?>> validationBundle : supportedValidation.entrySet()) {
+        switch (statementParserMode) {
+            case DEFAULT_MODE:
+                enabledSemanticVersions = false;
+                break;
+            case SEMVER_MODE:
+                enabledSemanticVersions = true;
+                break;
+            default:
+                throw new IllegalArgumentException("Unhandled parser mode " + statementParserMode);
+        }
+
+        for (final Entry<ValidationBundleType, Collection<?>> validationBundle : supportedValidation.entrySet()) {
             addToNs(ValidationBundlesNamespace.class, validationBundle.getKey(), validationBundle.getValue());
         }
 
-        addToNs(SupportedFeaturesNamespace.class, SupportedFeatures.SUPPORTED_FEATURES,
-                Preconditions.checkNotNull(isFeatureSupported, "Supported feature predicate must not be null."));
+        this.supportedVersions = ImmutableSet.copyOf(supports.get(ModelProcessingPhase.INIT).getSupportedVersions());
     }
 
-    public boolean isEnabledSemanticVersioning() {
+    boolean isEnabledSemanticVersioning() {
         return enabledSemanticVersions;
     }
 
-    public StatementSupportBundle getSupportsForPhase(final ModelProcessingPhase currentPhase) {
+    StatementSupportBundle getSupportsForPhase(final ModelProcessingPhase currentPhase) {
         return supports.get(currentPhase);
     }
 
-    public void addSource(@Nonnull final StatementStreamSource source) {
+    void addSource(@Nonnull final StatementStreamSource source) {
         sources.add(new SourceSpecificContext(this, source));
     }
 
+    void addLibSource(@Nonnull final StatementStreamSource libSource) {
+        Preconditions.checkState(!isEnabledSemanticVersioning(),
+                "Library sources are not supported in semantic version mode currently.");
+        Preconditions.checkState(currentPhase == ModelProcessingPhase.INIT,
+                "Add library source is allowed in ModelProcessingPhase.INIT only");
+        libSources.add(new SourceSpecificContext(this, libSource));
+    }
+
+    void setSupportedFeatures(final Set<QName> supportedFeatures) {
+        addToNs(SupportedFeaturesNamespace.class, SupportedFeatures.SUPPORTED_FEATURES,
+                    ImmutableSet.copyOf(supportedFeatures));
+    }
+
+    void setModulesDeviatedByModules(final Map<QNameModule, Set<QNameModule>> modulesDeviatedByModules) {
+        addToNs(ModulesDeviatedByModules.class, SupportedModules.SUPPORTED_MODULES,
+                    ImmutableMap.copyOf(modulesDeviatedByModules));
+    }
+
     @Override
     public StorageNodeType getStorageNodeType() {
         return StorageNodeType.GLOBAL;
@@ -130,7 +160,7 @@ class BuildGlobalContext extends NamespaceStorageSupport implements NamespaceBeh
             final Class<N> type) {
         NamespaceBehaviourWithListeners<?, ?, ?> potential = supportedNamespaces.get(type);
         if (potential == null) {
-            NamespaceBehaviour<K, V, N> potentialRaw = supports.get(currentPhase).getNamespaceBehaviour(type);
+            final NamespaceBehaviour<K, V, N> potentialRaw = supports.get(currentPhase).getNamespaceBehaviour(type);
             if (potentialRaw != null) {
                 potential = createNamespaceContext(potentialRaw);
                 supportedNamespaces.put(type, potential);
@@ -152,7 +182,7 @@ class BuildGlobalContext extends NamespaceStorageSupport implements NamespaceBeh
     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(
+            final VirtualNamespaceContext derivedContext = new VirtualNamespaceContext(
                     (DerivedNamespaceBehaviour) potentialRaw);
             getNamespaceBehaviour(((DerivedNamespaceBehaviour) potentialRaw).getDerivedFrom()).addDerivedNamespace(
                     derivedContext);
@@ -161,85 +191,121 @@ class BuildGlobalContext extends NamespaceStorageSupport implements NamespaceBeh
         return new SimpleNamespaceContext<>(potentialRaw);
     }
 
-    public StatementDefinitionContext<?, ?, ?> getStatementDefinition(final QName name) {
-        StatementDefinitionContext<?, ?, ?> potential = definitions.get(name);
+    StatementDefinitionContext<?, ?, ?> getStatementDefinition(final YangVersion version, final QName name) {
+        StatementDefinitionContext<?, ?, ?> potential = definitions.get(version, name);
         if (potential == null) {
-            StatementSupport<?, ?, ?> potentialRaw = supports.get(currentPhase).getStatementDefinition(name);
+            final StatementSupport<?, ?, ?> potentialRaw = supports.get(currentPhase).getStatementDefinition(version,
+                    name);
             if (potentialRaw != null) {
                 potential = new StatementDefinitionContext<>(potentialRaw);
-                definitions.put(name, potential);
+                definitions.put(version, name, potential);
             }
         }
         return potential;
     }
 
-    public EffectiveModelContext build() throws SourceException, ReactorException {
-        for (ModelProcessingPhase phase : PHASE_EXECUTION_ORDER) {
+    StatementDefinitionContext<?, ?, ?> getModelDefinedStatementDefinition(final QName name) {
+        return modelDefinedStmtDefs.get(name);
+    }
+
+    void putModelDefinedStatementDefinition(final QName name, final StatementDefinitionContext<?, ?, ?> def) {
+        modelDefinedStmtDefs.put(name, def);
+    }
+
+    private void executePhases() throws ReactorException {
+        for (final ModelProcessingPhase phase : PHASE_EXECUTION_ORDER) {
             startPhase(phase);
             loadPhaseStatements();
             completePhaseActions();
             endPhase(phase);
         }
+    }
+
+    EffectiveModelContext build() throws ReactorException {
+        executePhases();
         return transform();
     }
 
+    EffectiveSchemaContext buildEffective() throws ReactorException {
+        executePhases();
+        return transformEffective();
+    }
+
     private EffectiveModelContext transform() {
         Preconditions.checkState(finishedPhase == ModelProcessingPhase.EFFECTIVE_MODEL);
-        List<DeclaredStatement<?>> rootStatements = new ArrayList<>(sources.size());
-        for (SourceSpecificContext source : sources) {
+        final List<DeclaredStatement<?>> rootStatements = new ArrayList<>(sources.size());
+        for (final SourceSpecificContext source : sources) {
             rootStatements.add(source.getRoot().buildDeclared());
         }
         return new EffectiveModelContext(rootStatements);
     }
 
-    public EffectiveSchemaContext buildEffective() throws ReactorException {
-        for (ModelProcessingPhase phase : PHASE_EXECUTION_ORDER) {
-            startPhase(phase);
-            loadPhaseStatements();
-            completePhaseActions();
-            endPhase(phase);
+    private SomeModifiersUnresolvedException propagateException(final SourceSpecificContext source,
+            final RuntimeException cause) throws SomeModifiersUnresolvedException {
+        final SourceIdentifier sourceId = StmtContextUtils.createSourceIdentifier(source.getRoot());
+        if (!(cause instanceof SourceException)) {
+            /*
+             * This should not be happening as all our processing should provide SourceExceptions.
+             * We will wrap the exception to provide enough information to identify the problematic model,
+             * but also emit a warning so the offending codepath will get fixed.
+             */
+            LOG.warn("Unexpected error processing source {}. Please file an issue with this model attached.",
+                sourceId, cause);
         }
-        return transformEffective();
+
+        throw new SomeModifiersUnresolvedException(currentPhase, sourceId, cause);
     }
 
     private EffectiveSchemaContext transformEffective() throws ReactorException {
         Preconditions.checkState(finishedPhase == ModelProcessingPhase.EFFECTIVE_MODEL);
-        List<DeclaredStatement<?>> rootStatements = new ArrayList<>(sources.size());
-        List<EffectiveStatement<?, ?>> rootEffectiveStatements = new ArrayList<>(sources.size());
-        SourceIdentifier sourceId = null;
+        final List<DeclaredStatement<?>> rootStatements = new ArrayList<>(sources.size());
+        final List<EffectiveStatement<?, ?>> rootEffectiveStatements = new ArrayList<>(sources.size());
 
         try {
-            for (SourceSpecificContext source : sources) {
+            for (final SourceSpecificContext source : sources) {
                 final RootStatementContext<?, ?, ?> root = source.getRoot();
-                sourceId = Utils.createSourceIdentifier(root);
-                rootStatements.add(root.buildDeclared());
-                rootEffectiveStatements.add(root.buildEffective());
+                try {
+                    rootStatements.add(root.buildDeclared());
+                    rootEffectiveStatements.add(root.buildEffective());
+                } catch (final RuntimeException ex) {
+                    throw propagateException(source, ex);
+                }
             }
-        } catch (SourceException ex) {
-            throw new SomeModifiersUnresolvedException(currentPhase, sourceId, ex);
         } finally {
             RecursiveObjectLeaker.cleanup();
         }
 
+        sealMutableStatements();
         return new EffectiveSchemaContext(rootStatements, rootEffectiveStatements);
     }
 
     private void startPhase(final ModelProcessingPhase phase) {
         Preconditions.checkState(Objects.equals(finishedPhase, phase.getPreviousPhase()));
-        for (SourceSpecificContext source : sources) {
+        startPhaseFor(phase, sources);
+        startPhaseFor(phase, libSources);
+
+        currentPhase = phase;
+        LOG.debug("Global phase {} started", phase);
+    }
+
+    private static void startPhaseFor(final ModelProcessingPhase phase, final Set<SourceSpecificContext> sources) {
+        for (final SourceSpecificContext source : sources) {
             source.startPhase(phase);
         }
-        currentPhase = phase;
     }
 
     private void loadPhaseStatements() throws ReactorException {
         Preconditions.checkState(currentPhase != null);
-        for (SourceSpecificContext source : sources) {
+        loadPhaseStatementsFor(sources);
+        loadPhaseStatementsFor(libSources);
+    }
+
+    private void loadPhaseStatementsFor(final Set<SourceSpecificContext> sources) throws ReactorException {
+        for (final SourceSpecificContext source : sources) {
             try {
                 source.loadStatements();
-            } catch (SourceException ex) {
-                final SourceIdentifier sourceId = Utils.createSourceIdentifier(source.getRoot());
-                throw new SomeModifiersUnresolvedException(currentPhase, sourceId, ex);
+            } catch (final RuntimeException ex) {
+                throw propagateException(source, ex);
             }
         }
     }
@@ -247,12 +313,16 @@ class BuildGlobalContext extends NamespaceStorageSupport implements NamespaceBeh
     private SomeModifiersUnresolvedException addSourceExceptions(final List<SourceSpecificContext> sourcesToProgress) {
         boolean addedCause = false;
         SomeModifiersUnresolvedException buildFailure = null;
-        for (SourceSpecificContext failedSource : sourcesToProgress) {
-            final SourceException sourceEx = failedSource.failModifiers(currentPhase);
+        for (final SourceSpecificContext failedSource : sourcesToProgress) {
+            final Optional<SourceException> optSourceEx = failedSource.failModifiers(currentPhase);
+            if (!optSourceEx.isPresent()) {
+                continue;
+            }
 
+            final SourceException sourceEx = optSourceEx.get();
             // Workaround for broken logging implementations which ignore
             // suppressed exceptions
-            Throwable cause = sourceEx.getCause() != null ? sourceEx.getCause() : sourceEx;
+            final Throwable cause = sourceEx.getCause() != null ? sourceEx.getCause() : sourceEx;
             if (LOG.isDebugEnabled()) {
                 LOG.error("Failed to parse YANG from source {}", failedSource, sourceEx);
             } else {
@@ -264,7 +334,7 @@ class BuildGlobalContext extends NamespaceStorageSupport implements NamespaceBeh
                 LOG.error("{} additional errors reported:", suppressed.length);
 
                 int i = 1;
-                for (Throwable t : suppressed) {
+                for (final Throwable t : suppressed) {
                     // FIXME: this should be configured in the appender, really
                     if (LOG.isDebugEnabled()) {
                         LOG.error("Error {}: {}", i, t.getMessage(), t);
@@ -278,7 +348,7 @@ class BuildGlobalContext extends NamespaceStorageSupport implements NamespaceBeh
 
             if (!addedCause) {
                 addedCause = true;
-                final SourceIdentifier sourceId = Utils.createSourceIdentifier(failedSource.getRoot());
+                final SourceIdentifier sourceId = StmtContextUtils.createSourceIdentifier(failedSource.getRoot());
                 buildFailure = new SomeModifiersUnresolvedException(currentPhase, sourceId, sourceEx);
             } else {
                 buildFailure.addSuppressed(sourceEx);
@@ -289,49 +359,124 @@ class BuildGlobalContext extends NamespaceStorageSupport implements NamespaceBeh
 
     private void completePhaseActions() throws ReactorException {
         Preconditions.checkState(currentPhase != null);
-        List<SourceSpecificContext> sourcesToProgress = Lists.newArrayList(sources);
-        SourceIdentifier sourceId = null;
-        try {
-            boolean progressing = true;
-            while (progressing) {
-                // We reset progressing to false.
-                progressing = false;
-                Iterator<SourceSpecificContext> currentSource = sourcesToProgress.iterator();
-                while (currentSource.hasNext()) {
-                    SourceSpecificContext nextSourceCtx = currentSource.next();
-                    sourceId = Utils.createSourceIdentifier(nextSourceCtx.getRoot());
-                    PhaseCompletionProgress sourceProgress = nextSourceCtx.tryToCompletePhase(currentPhase);
+        final List<SourceSpecificContext> sourcesToProgress = Lists.newArrayList(sources);
+        if (!libSources.isEmpty()) {
+            Preconditions.checkState(currentPhase == ModelProcessingPhase.SOURCE_PRE_LINKAGE,
+                    "Yang library sources should be empty after ModelProcessingPhase.SOURCE_PRE_LINKAGE, "
+                            + "but current phase was %s", currentPhase);
+            sourcesToProgress.addAll(libSources);
+        }
+
+        boolean progressing = true;
+        while (progressing) {
+            // We reset progressing to false.
+            progressing = false;
+            final Iterator<SourceSpecificContext> currentSource = sourcesToProgress.iterator();
+            while (currentSource.hasNext()) {
+                final SourceSpecificContext nextSourceCtx = currentSource.next();
+                try {
+                    final PhaseCompletionProgress sourceProgress = nextSourceCtx.tryToCompletePhase(currentPhase);
                     switch (sourceProgress) {
-                    case FINISHED:
-                        currentSource.remove();
-                        // Fallback to progress, since we were able to make
-                        // progress in computation
-                    case PROGRESS:
-                        progressing = true;
-                        break;
-                    case NO_PROGRESS:
-                        // Noop
-                        break;
-                    default:
-                        throw new IllegalStateException("Unsupported phase progress " + sourceProgress);
+                        case FINISHED:
+                            currentSource.remove();
+                            // Fallback to progress, since we were able to make
+                            // progress in computation
+                        case PROGRESS:
+                            progressing = true;
+                            break;
+                        case NO_PROGRESS:
+                            // Noop
+                            break;
+                        default:
+                            throw new IllegalStateException("Unsupported phase progress " + sourceProgress);
                     }
+                } catch (final RuntimeException ex) {
+                    throw propagateException(nextSourceCtx, ex);
                 }
             }
-        } catch (SourceException e) {
-            throw new SomeModifiersUnresolvedException(currentPhase, sourceId, e);
         }
+
+        if (!libSources.isEmpty()) {
+            final Set<SourceSpecificContext> requiredLibs = getRequiredSourcesFromLib();
+            sources.addAll(requiredLibs);
+            libSources = ImmutableSet.of();
+            /*
+             * We want to report errors of relevant sources only, so any others can
+             * be removed.
+             */
+            sourcesToProgress.retainAll(sources);
+        }
+
         if (!sourcesToProgress.isEmpty()) {
             final SomeModifiersUnresolvedException buildFailure = addSourceExceptions(sourcesToProgress);
-            throw buildFailure;
+            if (buildFailure != null) {
+                throw buildFailure;
+            }
         }
     }
 
+    private Set<SourceSpecificContext> getRequiredSourcesFromLib() {
+        Preconditions.checkState(currentPhase == ModelProcessingPhase.SOURCE_PRE_LINKAGE,
+                "Required library sources can be collected only in ModelProcessingPhase.SOURCE_PRE_LINKAGE phase,"
+                        + " but current phase was %s", currentPhase);
+        final TreeBasedTable<String, Date, SourceSpecificContext> libSourcesTable = TreeBasedTable.create();
+        for (final SourceSpecificContext libSource : libSources) {
+            final ModuleIdentifier libSourceIdentifier = Preconditions.checkNotNull(libSource.getRootIdentifier());
+            libSourcesTable.put(libSourceIdentifier.getName(), libSourceIdentifier.getRevision(), libSource);
+        }
+
+        final Set<SourceSpecificContext> requiredLibs = new HashSet<>();
+        for (final SourceSpecificContext source : sources) {
+            collectRequiredSourcesFromLib(libSourcesTable, requiredLibs, source);
+        }
+        return requiredLibs;
+    }
+
+    private void collectRequiredSourcesFromLib(
+            final TreeBasedTable<String, Date, SourceSpecificContext> libSourcesTable,
+            final Set<SourceSpecificContext> requiredLibs, final SourceSpecificContext source) {
+        final Collection<ModuleIdentifier> requiredModules = source.getRequiredModules();
+        for (final ModuleIdentifier requiredModule : requiredModules) {
+            final SourceSpecificContext libSource = getRequiredLibSource(requiredModule, libSourcesTable);
+            if (libSource != null && requiredLibs.add(libSource)) {
+                collectRequiredSourcesFromLib(libSourcesTable, requiredLibs, libSource);
+            }
+        }
+    }
+
+    private static SourceSpecificContext getRequiredLibSource(final ModuleIdentifier requiredModule,
+            final TreeBasedTable<String, Date, SourceSpecificContext> libSourcesTable) {
+        return requiredModule.getRevision() == SimpleDateFormatUtil.DEFAULT_DATE_IMP ? getLatestRevision(libSourcesTable
+                .row(requiredModule.getName())) : libSourcesTable.get(requiredModule.getName(),
+                requiredModule.getRevision());
+    }
+
+    private static SourceSpecificContext getLatestRevision(final SortedMap<Date, SourceSpecificContext> sourceMap) {
+        return sourceMap != null && !sourceMap.isEmpty() ? sourceMap.get(sourceMap.lastKey()) : null;
+    }
+
     private void endPhase(final ModelProcessingPhase phase) {
         Preconditions.checkState(currentPhase == phase);
         finishedPhase = currentPhase;
+        LOG.debug("Global phase {} finished", phase);
     }
 
-    public Set<SourceSpecificContext> getSources() {
+    Set<SourceSpecificContext> getSources() {
         return sources;
     }
+
+    public Set<YangVersion> getSupportedVersions() {
+        return supportedVersions;
+    }
+
+    void addMutableStmtToSeal(final MutableStatement mutableStatement) {
+        mutableStatementsToSeal.add(mutableStatement);
+    }
+
+    void sealMutableStatements() {
+        for (final MutableStatement mutableStatement : mutableStatementsToSeal) {
+            mutableStatement.seal();
+        }
+        mutableStatementsToSeal.clear();
+    }
 }