X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=yang%2Fyang-parser-impl%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fyangtools%2Fyang%2Fparser%2Fstmt%2Freactor%2FBuildGlobalContext.java;h=ed7850df7aa34e3b08affe7a9453e97081134194;hb=b212baa59f859732bd3a799425bb420035fe6154;hp=cb0dd2c3437cf505d86b9ad120de14603bd7be09;hpb=481a692d463636bbcf75f023da71703913e1b605;p=yangtools.git diff --git a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/BuildGlobalContext.java b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/BuildGlobalContext.java index cb0dd2c343..ed7850df7a 100644 --- a/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/BuildGlobalContext.java +++ b/yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/stmt/reactor/BuildGlobalContext.java @@ -7,68 +7,135 @@ */ 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.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.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.Optional; import java.util.Set; +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.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.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.source.SupportedFeaturesNamespace.SupportedFeatures; +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.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 PHASE_EXECUTION_ORDER = ImmutableList.builder() - .add(ModelProcessingPhase.SOURCE_LINKAGE) - .add(ModelProcessingPhase.STATEMENT_DEFINITION) - .add(ModelProcessingPhase.FULL_DECLARATION) - .add(ModelProcessingPhase.EFFECTIVE_MODEL) - .build(); + private static final List PHASE_EXECUTION_ORDER = + ImmutableList.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> definitions = new HashMap<>(); - private final Map,NamespaceBehaviourWithListeners> namespaces = new HashMap<>(); + private final Table> definitions = HashBasedTable.create(); + private final Map> modelDefinedStmtDefs = new HashMap<>(); + private final Map, NamespaceBehaviourWithListeners> supportedNamespaces = new HashMap<>(); + private final List mutableStatementsToSeal = new ArrayList<>(); + private final Map supports; + private final Set sources = new HashSet<>(); + private final Set supportedVersions; + private final boolean enabledSemanticVersions; + private Set libSources = new HashSet<>(); + private ModelProcessingPhase currentPhase = ModelProcessingPhase.INIT; + private ModelProcessingPhase finishedPhase = ModelProcessingPhase.INIT; - private final Map supports; - private final Set sources = new HashSet<>(); + BuildGlobalContext(final Map supports, + final Map> supportedValidation, + final StatementParserMode statementParserMode) { + this.supports = Preconditions.checkNotNull(supports, "BuildGlobalContext#supports cannot be null"); - private ModelProcessingPhase currentPhase; - private ModelProcessingPhase finishedPhase; + switch (statementParserMode) { + case DEFAULT_MODE: + enabledSemanticVersions = false; + break; + case SEMVER_MODE: + enabledSemanticVersions = true; + break; + default: + throw new IllegalArgumentException("Unhandled parser mode " + statementParserMode); + } - public BuildGlobalContext(Map supports) { - super(); - this.supports = supports; + for (final Entry> validationBundle : supportedValidation.entrySet()) { + addToNs(ValidationBundlesNamespace.class, validationBundle.getKey(), validationBundle.getValue()); + } + + this.supportedVersions = ImmutableSet.copyOf(supports.get(ModelProcessingPhase.INIT).getSupportedVersions()); + } + + boolean isEnabledSemanticVersioning() { + return enabledSemanticVersions; } - public StatementSupportBundle getSupportsForPhase(ModelProcessingPhase currentPhase) { + StatementSupportBundle getSupportsForPhase(final ModelProcessingPhase currentPhase) { return supports.get(currentPhase); } - public void addSource(@Nonnull StatementStreamSource source) { - sources.add(new SourceSpecificContext(this,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 supportedFeatures) { + addToNs(SupportedFeaturesNamespace.class, SupportedFeatures.SUPPORTED_FEATURES, + ImmutableSet.copyOf(supportedFeatures)); + } + + void setModulesDeviatedByModules(final Map> modulesDeviatedByModules) { + addToNs(ModulesDeviatedByModules.class, SupportedModules.SUPPORTED_MODULES, + ImmutableMap.copyOf(modulesDeviatedByModules)); } @Override @@ -87,143 +154,349 @@ class BuildGlobalContext extends NamespaceStorageSupport implements NamespaceBeh } @Override - public > NamespaceBehaviourWithListeners getNamespaceBehaviour(Class type) { - NamespaceBehaviourWithListeners potential = namespaces.get(type); + public > NamespaceBehaviourWithListeners getNamespaceBehaviour( + final Class type) { + NamespaceBehaviourWithListeners potential = supportedNamespaces.get(type); if (potential == null) { - NamespaceBehaviour potentialRaw = supports.get(currentPhase).getNamespaceBehaviour(type); - if(potentialRaw != null) { - potential = new NamespaceBehaviourWithListeners<>(potentialRaw); - namespaces.put(type, potential); + final NamespaceBehaviour potentialRaw = supports.get(currentPhase).getNamespaceBehaviour(type); + 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 - */ - @SuppressWarnings("unchecked") - NamespaceBehaviourWithListeners casted = (NamespaceBehaviourWithListeners) potential; - return casted; + Verify.verify(type.equals(potential.getIdentifier())); + /* + * Safe cast, previous checkState checks equivalence of key from which + * type argument are derived + */ + return (NamespaceBehaviourWithListeners) potential; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private > NamespaceBehaviourWithListeners createNamespaceContext( + final NamespaceBehaviour potentialRaw) { + if (potentialRaw instanceof DerivedNamespaceBehaviour) { + final 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) { - StatementDefinitionContext potential = definitions.get(name); - if(potential == null) { - StatementSupport potentialRaw = supports.get(currentPhase).getStatementDefinition(name); - if(potentialRaw != null) { + StatementDefinitionContext getStatementDefinition(final YangVersion version, final QName name) { + StatementDefinitionContext potential = definitions.get(version, name); + if (potential == null) { + 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> rootStatements = new ArrayList<>(); - for(SourceSpecificContext source : sources) { - DeclaredStatement root = source.getRoot().buildDeclared(); - rootStatements.add(root); + final List> rootStatements = new ArrayList<>(sources.size()); + for (final SourceSpecificContext source : sources) { + rootStatements.add(source.getRoot().buildDeclared()); } return new EffectiveModelContext(rootStatements); } - public EffectiveSchemaContext buildEffective() throws SourceException, 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() { + @SuppressWarnings("checkstyle:illegalCatch") + private EffectiveSchemaContext transformEffective() throws ReactorException { Preconditions.checkState(finishedPhase == ModelProcessingPhase.EFFECTIVE_MODEL); - List> rootStatements = new ArrayList<>(); - List> rootEffectiveStatements = new ArrayList<>(); + final List> rootStatements = new ArrayList<>(sources.size()); + final List> 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 (final SourceSpecificContext source : sources) { + final RootStatementContext root = source.getRoot(); + try { + rootStatements.add(root.buildDeclared()); + rootEffectiveStatements.add(root.buildEffective()); + } catch (final RuntimeException ex) { + throw propagateException(source, ex); + } + } + } finally { + RecursiveObjectLeaker.cleanup(); } - return new EffectiveSchemaContext(rootStatements,rootEffectiveStatements); + sealMutableStatements(); + return EffectiveSchemaContext.create(rootStatements, rootEffectiveStatements); } - private void startPhase(ModelProcessingPhase phase) { + 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 sources) { + for (final SourceSpecificContext source : sources) { source.startPhase(phase); } - currentPhase = phase; } - private void loadPhaseStatements() throws SourceException { + private void loadPhaseStatements() throws ReactorException { Preconditions.checkState(currentPhase != null); - for(SourceSpecificContext source : sources) { - source.loadStatements(); + loadPhaseStatementsFor(sources); + loadPhaseStatementsFor(libSources); + } + + @SuppressWarnings("checkstyle:illegalCatch") + private void loadPhaseStatementsFor(final Set sources) throws ReactorException { + for (final SourceSpecificContext source : sources) { + try { + source.loadStatements(); + } catch (final RuntimeException ex) { + throw propagateException(source, ex); + } } } - private void completePhaseActions() throws ReactorException { + private SomeModifiersUnresolvedException addSourceExceptions(final List sourcesToProgress) { + boolean addedCause = false; + SomeModifiersUnresolvedException buildFailure = null; + for (final SourceSpecificContext failedSource : sourcesToProgress) { + final Optional optSourceEx = failedSource.failModifiers(currentPhase); + if (!optSourceEx.isPresent()) { + continue; + } + + final SourceException sourceEx = optSourceEx.get(); + // Workaround for broken logging implementations which ignore + // suppressed exceptions + final 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 count = 1; + for (final Throwable t : suppressed) { + // FIXME: this should be configured in the appender, really + if (LOG.isDebugEnabled()) { + LOG.error("Error {}: {}", count, t.getMessage(), t); + } else { + LOG.error("Error {}: {}", count, t.getMessage()); + } + + count++; + } + } + + if (!addedCause) { + addedCause = true; + 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); - ArrayList sourcesToProgress = Lists.newArrayList(sources); - try { - boolean progressing = true; - while(progressing) { - // We reset progressing to false. - progressing = false; - Iterator currentSource = sourcesToProgress.iterator(); - while(currentSource.hasNext()) { - PhaseCompletionProgress sourceProgress = currentSource.next().tryToCompletePhase(currentPhase); + final List 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 currentSource = sourcesToProgress.iterator(); + while (currentSource.hasNext()) { + final SourceSpecificContext nextSourceCtx = currentSource.next(); + try { + final PhaseCompletionProgress sourceProgress = nextSourceCtx.tryToCompletePhase(currentPhase); switch (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; + // Noop + break; + default: + throw new IllegalStateException("Unsupported phase progress " + sourceProgress); } + } catch (final RuntimeException ex) { + throw propagateException(nextSourceCtx, ex); } } - } catch (SourceException e) { - throw Throwables.propagate(e); - } - if(!sourcesToProgress.isEmpty()) { - SomeModifiersUnresolvedException buildFailure = new SomeModifiersUnresolvedException(currentPhase); - for(SourceSpecificContext failedSource : sourcesToProgress) { - SourceException sourceEx = failedSource.failModifiers(currentPhase); - buildFailure.addSuppressed(sourceEx); - } + } + + if (!libSources.isEmpty()) { + final Set 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) { throw buildFailure; + } + } + } + + private Set 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, 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 requiredLibs = new HashSet<>(); + for (final SourceSpecificContext source : sources) { + collectRequiredSourcesFromLib(libSourcesTable, requiredLibs, source); + removeConflictingLibSources(source, requiredLibs); + } + return requiredLibs; + } + + private void collectRequiredSourcesFromLib( + final TreeBasedTable, SourceSpecificContext> libSourcesTable, + final Set 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, SourceSpecificContext> libSourcesTable) { + return requiredSource.getRevision() == null ? getLatestRevision(libSourcesTable.row(requiredSource.getName())) + : libSourcesTable.get(requiredSource.getName(), + Optional.of(Revision.valueOf(requiredSource.getRevision()))); + } + + private static SourceSpecificContext getLatestRevision(final SortedMap, + 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 requiredLibs) { + final Iterator requiredLibsIter = requiredLibs.iterator(); + while (requiredLibsIter.hasNext()) { + final SourceSpecificContext currentReqSource = requiredLibsIter.next(); + if (source.getRootIdentifier().equals(currentReqSource.getRootIdentifier())) { + requiredLibsIter.remove(); + } } } - private void endPhase(ModelProcessingPhase phase) { + private void endPhase(final ModelProcessingPhase phase) { Preconditions.checkState(currentPhase == phase); finishedPhase = currentPhase; + LOG.debug("Global phase {} finished", phase); } - public Set getSources() { + Set getSources() { return sources; } + public Set getSupportedVersions() { + return supportedVersions; + } + + void addMutableStmtToSeal(final MutableStatement mutableStatement) { + mutableStatementsToSeal.add(mutableStatement); + } + + void sealMutableStatements() { + for (final MutableStatement mutableStatement : mutableStatementsToSeal) { + mutableStatement.seal(); + } + mutableStatementsToSeal.clear(); + } }