/*
* 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, E extends EffectiveStatement>
extends NamespaceStorageSupport implements StmtContext.Mutable {
/**
* 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 definition;
private final StatementSourceReference statementDeclSource;
private final String rawArgument;
private Multimap phaseListeners = ImmutableMultimap.of();
private Multimap phaseMutation = ImmutableMultimap.of();
private Collection> effective = ImmutableList.of();
private Collection> 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 def, final StatementSourceReference ref,
final String rawArgument) {
this.definition = Preconditions.checkNotNull(def);
this.statementDeclSource = Preconditions.checkNotNull(ref);
this.rawArgument = rawArgument;
}
StatementContextBase(final StatementContextBase 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> 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> 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> declaredSubstatements() {
return substatements.values();
}
@Nonnull
@Override
public Collection> effectiveSubstatements() {
if (effective instanceof ImmutableCollection) {
return effective;
}
return Collections.unmodifiableCollection(effective);
}
public void removeStatementsFromEffectiveSubstatements(final Collection> 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> 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> 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> 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 , CE extends EffectiveStatement> StatementContextBase createSubstatement(
final int offset, final StatementDefinitionContext 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> 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 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 openMutations = phaseMutation.get(phase);
if (!openMutations.isEmpty()) {
final Iterator 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 listeners = phaseListeners.get(phase);
if (listeners.isEmpty()) {
return;
}
final Iterator 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 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 > void onNamespaceElementAdded(final Class type, final K key, final V value) {
// definition().onNamespaceElementAdded(this, type, key, value);
}
> void onNamespaceItemAddedAction(final Class 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 behaviour = getBehaviourRegistry().getNamespaceBehaviour(type);
if (behaviour instanceof NamespaceBehaviourWithListeners) {
final NamespaceBehaviourWithListeners casted = (NamespaceBehaviourWithListeners) behaviour;
casted.addValueListener(new ValueAddedListener(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 Multimap 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 > void addContext(final Class 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);
}
}