/* * 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.MoreObjects; import com.google.common.base.MoreObjects.ToStringHelper; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumMap; import java.util.EventListener; import java.util.Iterator; import java.util.Optional; import javax.annotation.Nonnull; 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.StatementDefinition; import org.opendaylight.yangtools.yang.model.api.meta.StatementSource; import org.opendaylight.yangtools.yang.parser.spi.meta.CopyHistory; import org.opendaylight.yangtools.yang.parser.spi.meta.CopyType; 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.NamespaceBehaviour; import org.opendaylight.yangtools.yang.parser.spi.meta.StatementNamespace; import org.opendaylight.yangtools.yang.parser.spi.meta.StatementSupport; import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext; import org.opendaylight.yangtools.yang.parser.spi.source.SourceException; import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference; import org.opendaylight.yangtools.yang.parser.stmt.reactor.NamespaceBehaviourWithListeners.ValueAddedListener; public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E extends EffectiveStatement<A, D>> extends NamespaceStorageSupport implements StmtContext.Mutable<A, D, E> { /** * event listener when an item is added to model namespace */ interface OnNamespaceItemAdded extends EventListener { /** * @throws SourceException */ void namespaceItemAdded(StatementContextBase<?, ?, ?> context, Class<?> namespace, Object key, Object value); } /** * event listener when a parsing {@link ModelProcessingPhase} is completed */ interface OnPhaseFinished extends EventListener { /** * @throws SourceException */ boolean phaseFinished(StatementContextBase<?, ?, ?> context, ModelProcessingPhase phase); } /** * interface for all mutations within an {@link ModelActionBuilder.InferenceAction} */ interface ContextMutation { boolean isFinished(); } private final StatementDefinitionContext<A, D, E> definition; private final StatementSourceReference statementDeclSource; private final String rawArgument; private Multimap<ModelProcessingPhase, OnPhaseFinished> phaseListeners = ImmutableMultimap.of(); private Multimap<ModelProcessingPhase, ContextMutation> phaseMutation = ImmutableMultimap.of(); private Collection<StatementContextBase<?, ?, ?>> effective = ImmutableList.of(); private Collection<StatementContextBase<?, ?, ?>> effectOfStatement = ImmutableList.of(); private StatementMap substatements = StatementMap.empty(); private SupportedByFeatures supportedByFeatures = SupportedByFeatures.UNDEFINED; private CopyHistory copyHistory = CopyHistory.original(); private boolean isSupportedToBuildEffective = true; private ModelProcessingPhase completedPhase = null; private StatementContextBase<?, ?, ?> originalCtx; private D declaredInstance; private E effectiveInstance; private int order = 0; StatementContextBase(final StatementDefinitionContext<A, D, E> def, final StatementSourceReference ref, final String rawArgument) { this.definition = Preconditions.checkNotNull(def); this.statementDeclSource = Preconditions.checkNotNull(ref); this.rawArgument = rawArgument; } StatementContextBase(final StatementContextBase<A, D, E> original) { this.definition = Preconditions.checkNotNull(original.definition, "Statement context definition cannot be null copying from: %s", original.getStatementSourceReference()); this.statementDeclSource = Preconditions.checkNotNull(original.statementDeclSource, "Statement context statementDeclSource cannot be null copying from: %s", original.getStatementSourceReference()); this.rawArgument = original.rawArgument; } @Override public Collection<StatementContextBase<?, ?, ?>> getEffectOfStatement() { return effectOfStatement; } @Override public void addAsEffectOfStatement(final StatementContextBase<?, ?, ?> ctx) { if (effectOfStatement.isEmpty()) { effectOfStatement = new ArrayList<>(1); } effectOfStatement.add(ctx); } @Override public void addAsEffectOfStatement(final Collection<StatementContextBase<?, ?, ?>> ctxs) { if (ctxs.isEmpty()) { return; } if (effectOfStatement.isEmpty()) { effectOfStatement = new ArrayList<>(ctxs.size()); } effectOfStatement.addAll(ctxs); } @Override public SupportedByFeatures getSupportedByFeatures() { return supportedByFeatures; } @Override public void setSupportedByFeatures(final boolean isSupported) { this.supportedByFeatures = isSupported ? SupportedByFeatures.SUPPORTED : SupportedByFeatures.NOT_SUPPORTED; } @Override public boolean isSupportedToBuildEffective() { return isSupportedToBuildEffective; } @Override public void setIsSupportedToBuildEffective(final boolean isSupportedToBuildEffective) { this.isSupportedToBuildEffective = isSupportedToBuildEffective; } @Override public CopyHistory getCopyHistory() { return copyHistory; } @Override public void appendCopyHistory(final CopyType typeOfCopy, final CopyHistory toAppend) { copyHistory = copyHistory.append(typeOfCopy, toAppend); } @Override public StatementContextBase<?, ?, ?> getOriginalCtx() { return originalCtx; } @Override public void setOriginalCtx(final StatementContextBase<?, ?, ?> originalCtx) { this.originalCtx = originalCtx; } @Override public void setOrder(final int order) { this.order = order; } @Override public int getOrder() { return order; } @Override public ModelProcessingPhase getCompletedPhase() { return completedPhase; } @Override public void setCompletedPhase(final ModelProcessingPhase completedPhase) { this.completedPhase = completedPhase; } @Override public abstract StatementContextBase<?, ?, ?> getParentContext(); /** * @return root context of statement */ @Nonnull @Override public abstract RootStatementContext<?, ?, ?> getRoot(); /** * @return origin of statement */ @Nonnull @Override public StatementSource getStatementSource() { return statementDeclSource.getStatementSource(); } /** * @return reference of statement source */ @Nonnull @Override public StatementSourceReference getStatementSourceReference() { return statementDeclSource; } @Override public final String rawStatementArgument() { return rawArgument; } @Nonnull @Override public Collection<StatementContextBase<?, ?, ?>> declaredSubstatements() { return substatements.values(); } @Nonnull @Override public Collection<StatementContextBase<?, ?, ?>> effectiveSubstatements() { if (effective instanceof ImmutableCollection) { return effective; } return Collections.unmodifiableCollection(effective); } public void removeStatementsFromEffectiveSubstatements(final Collection<StatementContextBase<?, ?, ?>> substatements) { if (!effective.isEmpty()) { effective.removeAll(substatements); shrinkEffective(); } } private void shrinkEffective() { if (effective.isEmpty()) { effective = ImmutableList.of(); } } public void removeStatementFromEffectiveSubstatements(final StatementDefinition statementDef) { if (effective.isEmpty()) { return; } final Iterator<StatementContextBase<?, ?, ?>> iterator = effective.iterator(); while (iterator.hasNext()) { final StatementContextBase<?, ?, ?> next = iterator.next(); if (statementDef.equals(next.getPublicDefinition())) { iterator.remove(); } } shrinkEffective(); } /** * Removes a statement context from the effective substatements * based on its statement definition (i.e statement keyword) and raw (in String form) statement argument. * The statement context is removed only if both statement definition and statement argument match with * one of the effective substatements' statement definition and argument. * * If the statementArg parameter is null, the statement context is removed based only on its statement definition. * * @param statementDef statement definition of the statement context to remove * @param statementArg statement argument of the statement context to remove */ public void removeStatementFromEffectiveSubstatements(final StatementDefinition statementDef, final String statementArg) { if (statementArg == null) { removeStatementFromEffectiveSubstatements(statementDef); } if (effective.isEmpty()) { return; } final Iterator<StatementContextBase<?, ?, ?>> iterator = effective.iterator(); while (iterator.hasNext()) { final StatementContextBase<?, ?, ?> next = iterator.next(); if (statementDef.equals(next.getPublicDefinition()) && statementArg.equals(next.rawStatementArgument())) { iterator.remove(); } } shrinkEffective(); } /** * adds effective statement to collection of substatements * * @param substatement substatement * @throws IllegalStateException * if added in declared phase * @throws NullPointerException * if statement parameter is null */ public void addEffectiveSubstatement(final StatementContextBase<?, ?, ?> substatement) { Preconditions.checkNotNull(substatement, "StatementContextBase effective substatement cannot be null at: %s", getStatementSourceReference()); beforeAddEffectiveStatement(1); effective.add(substatement); } /** * adds effective statement to collection of substatements * * @param substatements substatements * @throws IllegalStateException * if added in declared phase * @throws NullPointerException * if statement parameter is null */ public void addEffectiveSubstatements(final Collection<StatementContextBase<?, ?, ?>> substatements) { if (substatements.isEmpty()) { return; } substatements.forEach(Preconditions::checkNotNull); beforeAddEffectiveStatement(substatements.size()); effective.addAll(substatements); } private void beforeAddEffectiveStatement(final int toAdd) { final ModelProcessingPhase inProgressPhase = getRoot().getSourceContext().getInProgressPhase(); Preconditions.checkState(inProgressPhase == ModelProcessingPhase.FULL_DECLARATION || inProgressPhase == ModelProcessingPhase.EFFECTIVE_MODEL, "Effective statement cannot be added in declared phase at: %s", getStatementSourceReference()); if (effective.isEmpty()) { effective = new ArrayList<>(toAdd); } } /** * Create a new substatement at the specified offset. * * @param offset Substatement offset * @param def definition context * @param ref source reference * @param argument statement argument * @return A new substatement */ public final <CA, CD extends DeclaredStatement<CA>, CE extends EffectiveStatement<CA, CD>> StatementContextBase<CA, CD, CE> createSubstatement( final int offset, final StatementDefinitionContext<CA, CD, CE> def, final StatementSourceReference ref, final String argument) { final ModelProcessingPhase inProgressPhase = getRoot().getSourceContext().getInProgressPhase(); Preconditions.checkState(inProgressPhase != ModelProcessingPhase.EFFECTIVE_MODEL, "Declared statement cannot be added in effective phase at: %s", getStatementSourceReference()); final Optional<StatementContextBase<?, ?, ?>> implicitStatement = definition.beforeSubStatementCreated(this, offset, def, ref, argument); if(implicitStatement.isPresent()) { final StatementContextBase<?, ?, ?> presentImplicitStmt = implicitStatement.get(); return presentImplicitStmt.createSubstatement(offset, def, ref, argument); } final StatementContextBase<CA, CD, CE> ret = new SubstatementContext<>(this, def, ref, argument); substatements = substatements.put(offset, ret); def.onStatementAdded(ret); return ret; } /** * Lookup substatement by its offset in this statement. * * @param offset Substatement offset * @return Substatement, or null if substatement does not exist. */ final StatementContextBase<?, ?, ?> lookupSubstatement(final int offset) { return substatements.get(offset); } /** * builds {@link DeclaredStatement} for statement context */ @Override public D buildDeclared() { Preconditions.checkArgument(completedPhase == ModelProcessingPhase.FULL_DECLARATION || completedPhase == ModelProcessingPhase.EFFECTIVE_MODEL); if (declaredInstance == null) { declaredInstance = definition().getFactory().createDeclared(this); } return declaredInstance; } /** * builds {@link EffectiveStatement} for statement context */ @Override public E buildEffective() { if (effectiveInstance == null) { effectiveInstance = definition().getFactory().createEffective(this); } return effectiveInstance; } /** * tries to execute current {@link ModelProcessingPhase} of source parsing * * @param phase * to be executed (completed) * @return if phase was successfully completed * @throws SourceException * when an error occured in source parsing */ boolean tryToCompletePhase(final ModelProcessingPhase phase) { boolean finished = true; final Collection<ContextMutation> openMutations = phaseMutation.get(phase); if (!openMutations.isEmpty()) { final Iterator<ContextMutation> it = openMutations.iterator(); while (it.hasNext()) { final ContextMutation current = it.next(); if (current.isFinished()) { it.remove(); } else { finished = false; } } if (openMutations.isEmpty()) { phaseMutation.removeAll(phase); if (phaseMutation.isEmpty()) { phaseMutation = ImmutableMultimap.of(); } } } for (final StatementContextBase<?, ?, ?> child : substatements.values()) { finished &= child.tryToCompletePhase(phase); } for (final StatementContextBase<?, ?, ?> child : effective) { finished &= child.tryToCompletePhase(phase); } if (finished) { onPhaseCompleted(phase); return true; } return false; } /** * Occurs on end of {@link ModelProcessingPhase} of source parsing * * @param phase * that was to be completed (finished) * @throws SourceException * when an error occurred in source parsing */ private void onPhaseCompleted(final ModelProcessingPhase phase) { completedPhase = phase; final Collection<OnPhaseFinished> listeners = phaseListeners.get(phase); if (listeners.isEmpty()) { return; } final Iterator<OnPhaseFinished> listener = listeners.iterator(); while (listener.hasNext()) { final OnPhaseFinished next = listener.next(); if (next.phaseFinished(this, phase)) { listener.remove(); } } if (listeners.isEmpty()) { phaseListeners.removeAll(phase); if (phaseListeners.isEmpty()) { phaseListeners = ImmutableMultimap.of(); } } } /** * Ends declared section of current node. * * @param ref * @throws SourceException */ void endDeclared(final StatementSourceReference ref, final ModelProcessingPhase phase) { definition().onDeclarationFinished(this, phase); } /** * @return statement definition */ protected final StatementDefinitionContext<A, D, E> definition() { return definition; } @Override protected void checkLocalNamespaceAllowed(final Class<? extends IdentifierNamespace<?, ?>> type) { definition().checkNamespaceAllowed(type); } /** * occurs when an item is added to model namespace * * @throws SourceException instance of SourceException */ @Override protected <K, V, N extends IdentifierNamespace<K, V>> void onNamespaceElementAdded(final Class<N> type, final K key, final V value) { // definition().onNamespaceElementAdded(this, type, key, value); } <K, V, N extends IdentifierNamespace<K, V>> void onNamespaceItemAddedAction(final Class<N> type, final K key, final OnNamespaceItemAdded listener) throws SourceException { final Object potential = getFromNamespace(type, key); if (potential != null) { listener.namespaceItemAdded(this, type, key, potential); return; } final NamespaceBehaviour<K, V, N> behaviour = getBehaviourRegistry().getNamespaceBehaviour(type); if (behaviour instanceof NamespaceBehaviourWithListeners) { final NamespaceBehaviourWithListeners<K, V, N> casted = (NamespaceBehaviourWithListeners<K, V, N>) behaviour; casted.addValueListener(new ValueAddedListener<K>(this, key) { @Override void onValueAdded(final Object key, final Object value) { try { listener.namespaceItemAdded(StatementContextBase.this, type, key, value); } catch (final SourceException e) { throw Throwables.propagate(e); } } }); } } /** * @see StatementSupport#getPublicView() */ @Nonnull @Override public StatementDefinition getPublicDefinition() { return definition().getPublicView(); } @Override public ModelActionBuilder newInferenceAction(final ModelProcessingPhase phase) { return getRoot().getSourceContext().newInferenceAction(phase); } private static <T> Multimap<ModelProcessingPhase, T> newMultimap() { return Multimaps.newListMultimap(new EnumMap<>(ModelProcessingPhase.class), () -> new ArrayList<>(1)); } /** * adds {@link OnPhaseFinished} listener for a {@link ModelProcessingPhase} end * * @throws SourceException */ void addPhaseCompletedListener(final ModelProcessingPhase phase, final OnPhaseFinished listener) { Preconditions.checkNotNull(phase, "Statement context processing phase cannot be null at: %s", getStatementSourceReference()); Preconditions.checkNotNull(listener, "Statement context phase listener cannot be null at: %s", getStatementSourceReference()); ModelProcessingPhase finishedPhase = completedPhase; while (finishedPhase != null) { if (phase.equals(finishedPhase)) { listener.phaseFinished(this, finishedPhase); return; } finishedPhase = finishedPhase.getPreviousPhase(); } if (phaseListeners.isEmpty()) { phaseListeners = newMultimap(); } phaseListeners.put(phase, listener); } /** * adds {@link ContextMutation} to {@link ModelProcessingPhase} * * @throws IllegalStateException * when the mutation was registered after phase was completed */ void addMutation(final ModelProcessingPhase phase, final ContextMutation mutation) { ModelProcessingPhase finishedPhase = completedPhase; while (finishedPhase != null) { if (phase.equals(finishedPhase)) { throw new IllegalStateException("Mutation registered after phase was completed at: " + getStatementSourceReference()); } finishedPhase = finishedPhase.getPreviousPhase(); } if (phaseMutation.isEmpty()) { phaseMutation = newMultimap(); } phaseMutation.put(phase, mutation); } /** * adds statement to namespace map with the key * * @param namespace * {@link StatementNamespace} child that determines namespace to be added to * @param key * of type according to namespace class specification * @param stmt * to be added to namespace map */ @Override public <K, KT extends K, N extends StatementNamespace<K, ?, ?>> void addContext(final Class<N> namespace, final KT key, final StmtContext<?, ?, ?> stmt) { addContextToNamespace(namespace, key, stmt); } @Override public final String toString() { return addToStringAttributes(MoreObjects.toStringHelper(this).omitNullValues()).toString(); } protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) { return toStringHelper.add("definition", definition).add("rawArgument", rawArgument); } }