/* * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ 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.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableTable; import com.google.common.collect.Multimap; import com.google.common.collect.Table; import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import javax.annotation.Nullable; import org.opendaylight.yangtools.concepts.Mutable; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.common.QNameModule; import org.opendaylight.yangtools.yang.common.YangConstants; import org.opendaylight.yangtools.yang.common.YangVersion; import org.opendaylight.yangtools.yang.model.api.ModuleIdentifier; import org.opendaylight.yangtools.yang.model.api.YangStmtMapping; 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.api.meta.StatementSource; import org.opendaylight.yangtools.yang.parser.spi.meta.InferenceException; import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder; 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; import org.opendaylight.yangtools.yang.parser.spi.meta.StatementDefinitionNamespace; 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.BelongsToModuleContext; import org.opendaylight.yangtools.yang.parser.spi.source.BelongsToPrefixToModuleIdentifier; import org.opendaylight.yangtools.yang.parser.spi.source.ImpPrefixToModuleIdentifier; import org.opendaylight.yangtools.yang.parser.spi.source.ImpPrefixToNamespace; import org.opendaylight.yangtools.yang.parser.spi.source.ImportedModuleContext; import org.opendaylight.yangtools.yang.parser.spi.source.ModuleIdentifierToModuleQName; import org.opendaylight.yangtools.yang.parser.spi.source.PrefixToModule; import org.opendaylight.yangtools.yang.parser.spi.source.PrefixToModuleMap; import org.opendaylight.yangtools.yang.parser.spi.source.QNameToStatementDefinition; import org.opendaylight.yangtools.yang.parser.spi.source.QNameToStatementDefinitionMap; import org.opendaylight.yangtools.yang.parser.spi.source.SourceException; import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference; import org.opendaylight.yangtools.yang.parser.spi.source.StatementStreamSource; import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.BitsSpecificationImpl; import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.Decimal64SpecificationImpl; import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.EnumSpecificationImpl; import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.IdentityRefSpecificationImpl; import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.InstanceIdentifierSpecificationImpl; import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.LeafrefSpecificationImpl; import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.ModelDefinedStatementDefinition; import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.TypeUtils; import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.UnionSpecificationImpl; import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.UnknownStatementImpl; import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.Utils; import org.opendaylight.yangtools.yang.parser.stmt.rfc7950.IdentityrefSpecificationRfc7950Support; import org.opendaylight.yangtools.yang.parser.stmt.rfc7950.LeafrefSpecificationRfc7950Support; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class SourceSpecificContext implements NamespaceStorageNode, NamespaceBehaviour.Registry, Mutable { public enum PhaseCompletionProgress { NO_PROGRESS, PROGRESS, FINISHED } private static final Logger LOG = LoggerFactory.getLogger(SourceSpecificContext.class); private static final Table> BUILTIN_TYPE_SUPPORTS = ImmutableTable.>builder() .put(YangVersion.VERSION_1, TypeUtils.DECIMAL64, new Decimal64SpecificationImpl.Definition()) .put(YangVersion.VERSION_1, TypeUtils.UNION, new UnionSpecificationImpl.Definition()) .put(YangVersion.VERSION_1, TypeUtils.ENUMERATION, new EnumSpecificationImpl.Definition()) .put(YangVersion.VERSION_1, TypeUtils.LEAF_REF, new LeafrefSpecificationImpl.Definition()) .put(YangVersion.VERSION_1_1, TypeUtils.LEAF_REF, new LeafrefSpecificationRfc7950Support()) .put(YangVersion.VERSION_1, TypeUtils.BITS, new BitsSpecificationImpl.Definition()) .put(YangVersion.VERSION_1, TypeUtils.IDENTITY_REF, new IdentityRefSpecificationImpl.Definition()) .put(YangVersion.VERSION_1_1, TypeUtils.IDENTITY_REF, new IdentityrefSpecificationRfc7950Support()) .put(YangVersion.VERSION_1, TypeUtils.INSTANCE_IDENTIFIER, new InstanceIdentifierSpecificationImpl.Definition()) .build(); private static final QName TYPE = YangStmtMapping.TYPE.getStatementName(); private final Multimap modifiers = HashMultimap.create(); private final QNameToStatementDefinitionMap qNameToStmtDefMap = new QNameToStatementDefinitionMap(); private final PrefixToModuleMap prefixToModuleMap = new PrefixToModuleMap(); private final BuildGlobalContext currentContext; private final StatementStreamSource source; /* * "imported" namespaces in this source -- this points to RootStatementContexts of * - modules imported via 'import' statement * - parent module, declared via 'belongs-to' statement */ private Collection> importedNamespaces = ImmutableList.of(); private ModelProcessingPhase finishedPhase = ModelProcessingPhase.INIT; private ModelProcessingPhase inProgressPhase; private RootStatementContext root; SourceSpecificContext(final BuildGlobalContext currentContext, final StatementStreamSource source) { this.currentContext = Preconditions.checkNotNull(currentContext); this.source = Preconditions.checkNotNull(source); } boolean isEnabledSemanticVersioning(){ return currentContext.isEnabledSemanticVersioning(); } ModelProcessingPhase getInProgressPhase() { return inProgressPhase; } StatementContextBase createDeclaredChild(final StatementContextBase current, final int childId, QName name, final String argument, final StatementSourceReference ref) { if (current != null) { // Fast path: we are entering a statement which was emitted in previous phase StatementContextBase existing = current.lookupSubstatement(childId); while (existing != null && StatementSource.CONTEXT == existing.getStatementSource()) { existing = existing.lookupSubstatement(childId); } if (existing != null) { return existing; } } // FIXME: BUG-7038: Refactor/clean up this special case if (TYPE.equals(name)) { SourceException.throwIfNull(argument, ref, "Type statement requires an argument"); if (TypeUtils.isYangTypeBodyStmtString(argument)) { name = QName.create(YangConstants.RFC6020_YIN_MODULE, argument); } else { name = QName.create(YangConstants.RFC6020_YIN_MODULE, TYPE.getLocalName()); } } StatementDefinitionContext def = currentContext.getStatementDefinition(getRootVersion(), name); if (def == null) { final StatementSupport extension = qNameToStmtDefMap.get(name); if (extension != null) { def = new StatementDefinitionContext<>(extension); } else { // type-body-stmts def = resolveTypeBodyStmts(name.getLocalName(), getRootVersion()); } } else if (current != null && current.definition().getRepresentingClass().equals(UnknownStatementImpl.class)) { /* * This code wraps statements encountered inside an extension so they do not get confused with regular * statements. * * FIXME: BUG-7037: re-evaluate whether this is really needed, as this is a very expensive way of making * this work. We really should be peeking into the extension definition to find these nodes, * as otherwise we are not reusing definitions nor support for these nodes. */ final QName qName = Utils.qNameFromArgument(current, name.getLocalName()); def = new StatementDefinitionContext<>(new UnknownStatementImpl.Definition( new ModelDefinedStatementDefinition(qName, argument != null))); } InferenceException.throwIfNull(def, ref, "Statement %s does not have type mapping defined.", name); if (def.hasArgument()) { SourceException.throwIfNull(argument, ref, "Statement %s requires an argument", name); } else { SourceException.throwIf(argument != null, ref, "Statement %s does not take argument", name); } if (current != null) { return current.createSubstatement(childId, def, ref, argument); } /* * If root is null or root version is other than default, * we need to create new root. */ if (root == null) { root = new RootStatementContext<>(this, def, ref, argument); } else if (!RootStatementContext.DEFAULT_VERSION.equals(root.getRootVersion()) && inProgressPhase == ModelProcessingPhase.SOURCE_LINKAGE) { root = new RootStatementContext<>(this, def, ref, argument, root.getRootVersion(), root.getRootIdentifier()); } else { final QName rootStatement = root.definition().getStatementName(); final String rootArgument = root.rawStatementArgument(); Preconditions.checkState(Objects.equals(def.getStatementName(), rootStatement) && Objects.equals(argument, rootArgument), "Root statement was already defined as '%s %s'.", rootStatement, rootArgument); } return root; } RootStatementContext getRoot() { return root; } /** * Return version of root statement context. * * @return version of root statement context */ YangVersion getRootVersion() { return root != null ? root.getRootVersion() : RootStatementContext.DEFAULT_VERSION; } DeclaredStatement buildDeclared() { return root.buildDeclared(); } EffectiveStatement buildEffective() { return root.buildEffective(); } void startPhase(final ModelProcessingPhase phase) { @Nullable final ModelProcessingPhase previousPhase = phase.getPreviousPhase(); Verify.verify(Objects.equals(previousPhase, finishedPhase), "Phase sequencing violation: previous phase should be %s, source %s has %s", previousPhase, source, finishedPhase); final Collection previousModifiers = modifiers.get(previousPhase); Preconditions.checkState(previousModifiers.isEmpty(), "Previous phase %s has unresolved modifiers %s in source %s", previousPhase, previousModifiers, source); inProgressPhase = phase; LOG.debug("Source {} started phase {}", source, phase); } private void updateImportedNamespaces(final Class type, final Object value) { if (BelongsToModuleContext.class.isAssignableFrom(type) || ImportedModuleContext.class.isAssignableFrom(type)) { if (importedNamespaces.isEmpty()) { importedNamespaces = new ArrayList<>(1); } Verify.verify(value instanceof RootStatementContext); importedNamespaces.add((RootStatementContext) value); } } @Override public > V putToLocalStorage(final Class type, final K key, final V value) { // RootStatementContext takes care of IncludedModuleContext and the rest... final V ret = getRoot().putToLocalStorage(type, key, value); // FIXME: what about duplicates? updateImportedNamespaces(type, value); return ret; } @Override public > V putToLocalStorageIfAbsent(final Class type, final K key, final V value) { // RootStatementContext takes care of IncludedModuleContext and the rest... final V ret = getRoot().putToLocalStorageIfAbsent(type, key, value); if (ret == null) { updateImportedNamespaces(type, value); } return ret; } @Override public StorageNodeType getStorageNodeType() { return StorageNodeType.SOURCE_LOCAL_SPECIAL; } @Override public > V getFromLocalStorage(final Class type, final K key) { final V potentialLocal = getRoot().getFromLocalStorage(type, key); if (potentialLocal != null) { return potentialLocal; } for (final NamespaceStorageNode importedSource : importedNamespaces) { final V potential = importedSource.getFromLocalStorage(type, key); if (potential != null) { return potential; } } return null; } @Nullable @Override public > Map getAllFromLocalStorage(final Class type) { final Map potentialLocal = getRoot().getAllFromLocalStorage(type); if (potentialLocal != null) { return potentialLocal; } for (final NamespaceStorageNode importedSource : importedNamespaces) { final Map potential = importedSource.getAllFromLocalStorage(type); if (potential != null) { return potential; } } return null; } @Override public > NamespaceBehaviour getNamespaceBehaviour( final Class type) { return currentContext.getNamespaceBehaviour(type); } @Override public NamespaceStorageNode getParentNamespaceStorage() { return currentContext; } PhaseCompletionProgress tryToCompletePhase(final ModelProcessingPhase phase) throws SourceException { final Collection currentPhaseModifiers = modifiers.get(phase); boolean hasProgressed = tryToProgress(currentPhaseModifiers); Preconditions.checkNotNull(this.root, "Malformed source. Valid root element is missing."); final boolean phaseCompleted = root.tryToCompletePhase(phase); hasProgressed |= tryToProgress(currentPhaseModifiers); if (phaseCompleted && currentPhaseModifiers.isEmpty()) { finishedPhase = phase; LOG.debug("Source {} finished phase {}", source, phase); return PhaseCompletionProgress.FINISHED; } return hasProgressed ? PhaseCompletionProgress.PROGRESS : PhaseCompletionProgress.NO_PROGRESS; } private static boolean tryToProgress(final Collection currentPhaseModifiers) { boolean hasProgressed = false; final Iterator modifier = currentPhaseModifiers.iterator(); while (modifier.hasNext()) { if (modifier.next().tryApply()) { modifier.remove(); hasProgressed = true; } } return hasProgressed; } ModelActionBuilder newInferenceAction(final ModelProcessingPhase phase) { final ModifierImpl action = new ModifierImpl(phase); modifiers.put(phase, action); return action; } @Override public String toString() { return "SourceSpecificContext [source=" + source + ", current=" + inProgressPhase + ", finished=" + finishedPhase + "]"; } Optional failModifiers(final ModelProcessingPhase identifier) { final List exceptions = new ArrayList<>(); for (final ModifierImpl mod : modifiers.get(identifier)) { try { mod.failModifier(); } catch (final SourceException e) { exceptions.add(e); } } if (exceptions.isEmpty()) { return Optional.empty(); } final String message = String.format("Yang model processing phase %s failed", identifier); final InferenceException e = new InferenceException(message, root.getStatementSourceReference(), exceptions.get(0)); exceptions.listIterator(1).forEachRemaining(e::addSuppressed); return Optional.of(e); } void loadStatements() throws SourceException { LOG.trace("Source {} loading statements for phase {}", source, inProgressPhase); switch (inProgressPhase) { case SOURCE_PRE_LINKAGE: source.writePreLinkage(new StatementContextWriter(this, inProgressPhase), stmtDef()); break; case SOURCE_LINKAGE: source.writeLinkage(new StatementContextWriter(this, inProgressPhase), stmtDef(), preLinkagePrefixes(), getRootVersion()); break; case STATEMENT_DEFINITION: source.writeLinkageAndStatementDefinitions(new StatementContextWriter(this, inProgressPhase), stmtDef(), prefixes(), getRootVersion()); break; case FULL_DECLARATION: source.writeFull(new StatementContextWriter(this, inProgressPhase), stmtDef(), prefixes(), getRootVersion()); break; default: break; } } private static StatementDefinitionContext resolveTypeBodyStmts(final String typeArgument, final YangVersion version) { StatementSupport support = BUILTIN_TYPE_SUPPORTS.get(version, typeArgument); if (support == null) { support = BUILTIN_TYPE_SUPPORTS.get(YangVersion.VERSION_1, typeArgument); } return support == null ? null : new StatementDefinitionContext<>(support); } private PrefixToModule preLinkagePrefixes() { final PrefixToModuleMap preLinkagePrefixes = new PrefixToModuleMap(true); final Map prefixToNamespaceMap = getAllFromLocalStorage(ImpPrefixToNamespace.class); if (prefixToNamespaceMap == null) { //:FIXME if it is a submodule without any import, the map is null. Handle also submodules and includes... return null; } prefixToNamespaceMap.forEach((key, value) -> preLinkagePrefixes.put(key, QNameModule.create(value, null))); return preLinkagePrefixes; } private PrefixToModule prefixes() { final Map allPrefixes = getRoot().getAllFromNamespace(ImpPrefixToModuleIdentifier .class); final Map belongsToPrefixes = getRoot().getAllFromNamespace (BelongsToPrefixToModuleIdentifier.class); if (belongsToPrefixes != null) { allPrefixes.putAll(belongsToPrefixes); } allPrefixes.forEach((key, value) -> prefixToModuleMap.put(key, getRoot().getFromNamespace(ModuleIdentifierToModuleQName.class, value))); return prefixToModuleMap; } private QNameToStatementDefinition stmtDef() { // regular YANG statements and extension supports added final StatementSupportBundle supportsForPhase = currentContext.getSupportsForPhase(inProgressPhase); qNameToStmtDefMap.putAll(supportsForPhase.getCommonDefinitions()); qNameToStmtDefMap.putAll(supportsForPhase.getDefinitionsSpecificForVersion(getRootVersion())); // No further actions needed if (inProgressPhase != ModelProcessingPhase.FULL_DECLARATION) { return qNameToStmtDefMap; } // We need to any and all extension statements which have been declared in the context final Map> extensions = currentContext.getAllFromNamespace( StatementDefinitionNamespace.class); if (extensions != null) { extensions.forEach((qname, support) -> { final StatementSupport existing = qNameToStmtDefMap.putIfAbsent(qname, support); if (existing != null) { LOG.debug("Source {} already defines statement {} as {}", source, qname, existing); } else { LOG.debug("Source {} defined statement {} as {}", source, qname, support); } }); } return qNameToStmtDefMap; } public Set getSupportedVersions() { return currentContext.getSupportedVersions(); } void addMutableStmtToSeal(final MutableStatement mutableStatement) { currentContext.addMutableStmtToSeal(mutableStatement); } Collection getRequiredModules() { return root.getRequiredModules(); } ModuleIdentifier getRootIdentifier() { return root.getRootIdentifier(); } }