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.Lists;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Table;
+import com.google.common.collect.TreeBasedTable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
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.Revision;
+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;
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.Registry;
import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.StorageNodeType;
import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceNotAvailableException;
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;
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;
-class BuildGlobalContext extends NamespaceStorageSupport implements NamespaceBehaviour.Registry {
+class BuildGlobalContext extends NamespaceStorageSupport implements Registry {
private static final Logger LOG = LoggerFactory.getLogger(BuildGlobalContext.class);
- private static final List<ModelProcessingPhase> PHASE_EXECUTION_ORDER = ImmutableList
- .<ModelProcessingPhase> builder().add(ModelProcessingPhase.SOURCE_PRE_LINKAGE)
+ private static final List<ModelProcessingPhase> PHASE_EXECUTION_ORDER =
+ ImmutableList.<ModelProcessingPhase>builder().add(ModelProcessingPhase.SOURCE_PRE_LINKAGE)
.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;
-
- 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."));
- }
-
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;
+
+ 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());
}
boolean isEnabledSemanticVersioning() {
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;
return new SimpleNamespaceContext<>(potentialRaw);
}
- 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) {
- final 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;
}
- EffectiveModelContext build() throws SourceException, ReactorException {
+ 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);
final List<DeclaredStatement<?>> rootStatements = new ArrayList<>(sources.size());
return new EffectiveModelContext(rootStatements);
}
- EffectiveSchemaContext buildEffective() throws ReactorException {
- for (final 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);
}
+ @SuppressWarnings("checkstyle:illegalCatch")
private EffectiveSchemaContext transformEffective() throws ReactorException {
Preconditions.checkState(finishedPhase == ModelProcessingPhase.EFFECTIVE_MODEL);
final List<DeclaredStatement<?>> rootStatements = new ArrayList<>(sources.size());
final List<EffectiveStatement<?, ?>> rootEffectiveStatements = new ArrayList<>(sources.size());
- SourceIdentifier sourceId = null;
try {
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 (final SourceException ex) {
- throw new SomeModifiersUnresolvedException(currentPhase, sourceId, ex);
} finally {
RecursiveObjectLeaker.cleanup();
}
- return new EffectiveSchemaContext(rootStatements, rootEffectiveStatements);
+ sealMutableStatements();
+ return EffectiveSchemaContext.create(rootStatements, rootEffectiveStatements);
}
private void startPhase(final ModelProcessingPhase phase) {
Preconditions.checkState(Objects.equals(finishedPhase, phase.getPreviousPhase()));
+ 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;
- LOG.debug("Global phase {} started", phase);
}
private void loadPhaseStatements() throws ReactorException {
Preconditions.checkState(currentPhase != null);
+ loadPhaseStatementsFor(sources);
+ loadPhaseStatementsFor(libSources);
+ }
+
+ @SuppressWarnings("checkstyle:illegalCatch")
+ private void loadPhaseStatementsFor(final Set<SourceSpecificContext> sources) throws ReactorException {
for (final SourceSpecificContext source : sources) {
try {
source.loadStatements();
- } catch (final SourceException ex) {
- final SourceIdentifier sourceId = Utils.createSourceIdentifier(source.getRoot());
- throw new SomeModifiersUnresolvedException(currentPhase, sourceId, ex);
+ } catch (final RuntimeException ex) {
+ throw propagateException(source, ex);
}
}
}
if (suppressed.length > 0) {
LOG.error("{} additional errors reported:", suppressed.length);
- int i = 1;
+ int count = 1;
for (final Throwable t : suppressed) {
// FIXME: this should be configured in the appender, really
if (LOG.isDebugEnabled()) {
- LOG.error("Error {}: {}", i, t.getMessage(), t);
+ LOG.error("Error {}: {}", count, t.getMessage(), t);
} else {
- LOG.error("Error {}: {}", i, t.getMessage());
+ LOG.error("Error {}: {}", count, t.getMessage());
}
- i++;
+ count++;
}
}
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);
return buildFailure;
}
+ @SuppressWarnings("checkstyle:illegalCatch")
private void completePhaseActions() throws ReactorException {
Preconditions.checkState(currentPhase != null);
- final List<SourceSpecificContext> sourcesToProgress = Lists.newArrayList(sources);
- SourceIdentifier sourceId = null;
- try {
- 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();
- sourceId = Utils.createSourceIdentifier(nextSourceCtx.getRoot());
+ final List<SourceSpecificContext> sourcesToProgress = new ArrayList<>(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();
+ // we were able to make progress in computation
+ progressing = true;
+ break;
+ 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 (final 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);
if (buildFailure != null) {
}
}
+ 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, Optional<Revision>, SourceSpecificContext> libSourcesTable = TreeBasedTable.create(
+ String::compareTo, Revision::compare);
+ 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);
+ removeConflictingLibSources(source, requiredLibs);
+ }
+ return requiredLibs;
+ }
+
+ private void collectRequiredSourcesFromLib(
+ final TreeBasedTable<String, Optional<Revision>, SourceSpecificContext> libSourcesTable,
+ final Set<SourceSpecificContext> requiredLibs, final SourceSpecificContext source) {
+ for (final SourceIdentifier requiredSource : source.getRequiredSources()) {
+ final SourceSpecificContext libSource = getRequiredLibSource(requiredSource, libSourcesTable);
+ if (libSource != null && requiredLibs.add(libSource)) {
+ collectRequiredSourcesFromLib(libSourcesTable, requiredLibs, libSource);
+ }
+ }
+ }
+
+ private static SourceSpecificContext getRequiredLibSource(final SourceIdentifier requiredSource,
+ final TreeBasedTable<String, Optional<Revision>, SourceSpecificContext> libSourcesTable) {
+ return requiredSource.getRevision().isPresent()
+ ? libSourcesTable.get(requiredSource.getName(), requiredSource.getRevision())
+ : getLatestRevision(libSourcesTable.row(requiredSource.getName()));
+ }
+
+ private static SourceSpecificContext getLatestRevision(final SortedMap<Optional<Revision>,
+ SourceSpecificContext> sourceMap) {
+ return sourceMap != null && !sourceMap.isEmpty() ? sourceMap.get(sourceMap.lastKey()) : null;
+ }
+
+ // removes required library sources which would cause namespace/name conflict with one of the main sources
+ // later in the parsing process. this can happen if we add a parent module or a submodule as a main source
+ // and the same parent module or submodule is added as one of the library sources.
+ // such situation may occur when using the yang-system-test artifact - if a parent module/submodule is specified
+ // as its argument and the same dir is specified as one of the library dirs through -p option).
+ private static void removeConflictingLibSources(final SourceSpecificContext source,
+ final Set<SourceSpecificContext> requiredLibs) {
+ final Iterator<SourceSpecificContext> requiredLibsIter = requiredLibs.iterator();
+ while (requiredLibsIter.hasNext()) {
+ final SourceSpecificContext currentReqSource = requiredLibsIter.next();
+ if (source.getRootIdentifier().equals(currentReqSource.getRootIdentifier())) {
+ requiredLibsIter.remove();
+ }
+ }
+ }
+
private void endPhase(final ModelProcessingPhase phase) {
Preconditions.checkState(currentPhase == phase);
finishedPhase = currentPhase;
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();
+ }
}