BUG-4688: Add flexible match support to NamespaceStorageSupport
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / stmt / reactor / StatementContextBase.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.yang.parser.stmt.reactor;
9
10 import com.google.common.base.MoreObjects;
11 import com.google.common.base.MoreObjects.ToStringHelper;
12 import com.google.common.base.Preconditions;
13 import com.google.common.collect.ImmutableCollection;
14 import com.google.common.collect.ImmutableList;
15 import com.google.common.collect.ImmutableMultimap;
16 import com.google.common.collect.ImmutableSet;
17 import com.google.common.collect.Multimap;
18 import com.google.common.collect.Multimaps;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.EnumMap;
23 import java.util.EventListener;
24 import java.util.Iterator;
25 import java.util.Map.Entry;
26 import java.util.Optional;
27 import java.util.Set;
28 import javax.annotation.Nonnull;
29 import javax.annotation.Nullable;
30 import org.opendaylight.yangtools.util.OptionalBoolean;
31 import org.opendaylight.yangtools.yang.common.QName;
32 import org.opendaylight.yangtools.yang.common.QNameModule;
33 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
34 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
35 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
36 import org.opendaylight.yangtools.yang.model.api.meta.IdentifierNamespace;
37 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
38 import org.opendaylight.yangtools.yang.model.api.meta.StatementSource;
39 import org.opendaylight.yangtools.yang.parser.spi.meta.CopyHistory;
40 import org.opendaylight.yangtools.yang.parser.spi.meta.CopyType;
41 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder;
42 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase;
43 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour;
44 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceKeyCriterion;
45 import org.opendaylight.yangtools.yang.parser.spi.meta.StatementNamespace;
46 import org.opendaylight.yangtools.yang.parser.spi.meta.StatementSupport;
47 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
48 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext.Mutable;
49 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
50 import org.opendaylight.yangtools.yang.parser.spi.source.ImplicitSubstatement;
51 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
52 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
53 import org.opendaylight.yangtools.yang.parser.spi.source.SupportedFeaturesNamespace;
54 import org.opendaylight.yangtools.yang.parser.spi.source.SupportedFeaturesNamespace.SupportedFeatures;
55 import org.opendaylight.yangtools.yang.parser.stmt.reactor.NamespaceBehaviourWithListeners.KeyedValueAddedListener;
56 import org.opendaylight.yangtools.yang.parser.stmt.reactor.NamespaceBehaviourWithListeners.PredicateValueAddedListener;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59
60 public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E extends EffectiveStatement<A, D>>
61         extends NamespaceStorageSupport implements Mutable<A, D, E> {
62     /**
63      * Event listener when an item is added to model namespace.
64      */
65     interface OnNamespaceItemAdded extends EventListener {
66         /**
67          * Invoked whenever a new item is added to a namespace.
68          */
69         void namespaceItemAdded(StatementContextBase<?, ?, ?> context, Class<?> namespace, Object key, Object value);
70     }
71
72     /**
73      * Event listener when a parsing {@link ModelProcessingPhase} is completed.
74      */
75     interface OnPhaseFinished extends EventListener {
76         /**
77          * Invoked whenever a processing phase has finished.
78          */
79         boolean phaseFinished(StatementContextBase<?, ?, ?> context, ModelProcessingPhase phase);
80     }
81
82     /**
83      * Interface for all mutations within an {@link ModelActionBuilder.InferenceAction}.
84      */
85     interface ContextMutation {
86
87         boolean isFinished();
88     }
89
90     private static final Logger LOG = LoggerFactory.getLogger(StatementContextBase.class);
91
92     private final StatementDefinitionContext<A, D, E> definition;
93     private final StatementSourceReference statementDeclSource;
94     private final StmtContext<?, ?, ?> originalCtx;
95     private final CopyHistory copyHistory;
96     private final String rawArgument;
97
98     private Multimap<ModelProcessingPhase, OnPhaseFinished> phaseListeners = ImmutableMultimap.of();
99     private Multimap<ModelProcessingPhase, ContextMutation> phaseMutation = ImmutableMultimap.of();
100     private Collection<Mutable<?, ?, ?>> effective = ImmutableList.of();
101     private Collection<StmtContext<?, ?, ?>> effectOfStatement = ImmutableList.of();
102     private StatementMap substatements = StatementMap.empty();
103
104     private boolean isSupportedToBuildEffective = true;
105     private ModelProcessingPhase completedPhase = null;
106     private D declaredInstance;
107     private E effectiveInstance;
108
109     // BooleanFields value
110     private byte supportedByFeatures;
111
112     StatementContextBase(final StatementDefinitionContext<A, D, E> def, final StatementSourceReference ref,
113             final String rawArgument) {
114         this.definition = Preconditions.checkNotNull(def);
115         this.statementDeclSource = Preconditions.checkNotNull(ref);
116         this.rawArgument = def.internArgument(rawArgument);
117         this.copyHistory = CopyHistory.original();
118         this.originalCtx = null;
119     }
120
121     StatementContextBase(final StatementContextBase<A, D, E> original, final CopyType copyType) {
122         this.definition = Preconditions.checkNotNull(original.definition,
123                 "Statement context definition cannot be null copying from: %s", original.getStatementSourceReference());
124         this.statementDeclSource = Preconditions.checkNotNull(original.statementDeclSource,
125                 "Statement context statementDeclSource cannot be null copying from: %s",
126                 original.getStatementSourceReference());
127         this.rawArgument = original.rawArgument;
128         this.copyHistory = CopyHistory.of(copyType, original.getCopyHistory());
129         this.originalCtx = original.getOriginalCtx().orElse(original);
130     }
131
132     @Override
133     public Collection<? extends StmtContext<?, ?, ?>> getEffectOfStatement() {
134         return effectOfStatement;
135     }
136
137     @Override
138     public void addAsEffectOfStatement(final StmtContext<?, ?, ?> ctx) {
139         if (effectOfStatement.isEmpty()) {
140             effectOfStatement = new ArrayList<>(1);
141         }
142         effectOfStatement.add(ctx);
143     }
144
145     @Override
146     public void addAsEffectOfStatement(final Collection<? extends StmtContext<?, ?, ?>> ctxs) {
147         if (ctxs.isEmpty()) {
148             return;
149         }
150
151         if (effectOfStatement.isEmpty()) {
152             effectOfStatement = new ArrayList<>(ctxs.size());
153         }
154         effectOfStatement.addAll(ctxs);
155     }
156
157     @Override
158     public boolean isSupportedByFeatures() {
159         if (OptionalBoolean.isPresent(supportedByFeatures)) {
160             return OptionalBoolean.get(supportedByFeatures);
161         }
162
163         if (isIgnoringIfFeatures()) {
164             supportedByFeatures = OptionalBoolean.of(true);
165             return true;
166         }
167
168         final boolean isParentSupported = isParentSupportedByFeatures();
169         /*
170          * If parent is not supported, then this context is also not supported.
171          * So we do not need to check if-features statements of this context and
172          * we can return false immediately.
173          */
174         if (!isParentSupported) {
175             supportedByFeatures = OptionalBoolean.of(false);
176             return false;
177         }
178
179         /*
180          * If parent is supported, we need to check if-features statements of
181          * this context.
182          */
183         // If the set of supported features has not been provided, all features are supported by default.
184         final Set<QName> supportedFeatures = getFromNamespace(SupportedFeaturesNamespace.class,
185                 SupportedFeatures.SUPPORTED_FEATURES);
186         final boolean ret = supportedFeatures == null ? true
187                 : StmtContextUtils.checkFeatureSupport(this, supportedFeatures);
188
189         supportedByFeatures = OptionalBoolean.of(ret);
190         return ret;
191     }
192
193     protected abstract boolean isParentSupportedByFeatures();
194
195     protected abstract boolean isIgnoringIfFeatures();
196
197     protected abstract boolean isIgnoringConfig();
198
199     @Override
200     public boolean isSupportedToBuildEffective() {
201         return isSupportedToBuildEffective;
202     }
203
204     @Override
205     public void setIsSupportedToBuildEffective(final boolean isSupportedToBuildEffective) {
206         this.isSupportedToBuildEffective = isSupportedToBuildEffective;
207     }
208
209     @Override
210     public CopyHistory getCopyHistory() {
211         return copyHistory;
212     }
213
214     @Override
215     public Optional<StmtContext<?, ?, ?>> getOriginalCtx() {
216         return Optional.ofNullable(originalCtx);
217     }
218
219     @Override
220     public ModelProcessingPhase getCompletedPhase() {
221         return completedPhase;
222     }
223
224     @Override
225     public void setCompletedPhase(final ModelProcessingPhase completedPhase) {
226         this.completedPhase = completedPhase;
227     }
228
229     @Override
230     public abstract StatementContextBase<?, ?, ?> getParentContext();
231
232     /**
233      * Returns the model root for this statement.
234      *
235      * @return root context of statement
236      */
237     @Nonnull
238     @Override
239     public abstract RootStatementContext<?, ?, ?> getRoot();
240
241     /**
242      * Returns the origin of the statement.
243      *
244      * @return origin of statement
245      */
246     @Nonnull
247     @Override
248     public StatementSource getStatementSource() {
249         return statementDeclSource.getStatementSource();
250     }
251
252     /**
253      * Returns a reference to statement source.
254      *
255      * @return reference of statement source
256      */
257     @Nonnull
258     @Override
259     public StatementSourceReference getStatementSourceReference() {
260         return statementDeclSource;
261     }
262
263     @Override
264     public final String rawStatementArgument() {
265         return rawArgument;
266     }
267
268     @Nonnull
269     @Override
270     public Collection<? extends StmtContext<?, ?, ?>> declaredSubstatements() {
271         return substatements.values();
272     }
273
274     @Nonnull
275     @Override
276     public Collection<? extends Mutable<?, ?, ?>> mutableDeclaredSubstatements() {
277         return substatements.values();
278     }
279
280     @Override
281     public Collection<? extends StmtContext<?, ?, ?>> effectiveSubstatements() {
282         return mutableEffectiveSubstatements();
283     }
284
285     @Nonnull
286     @Override
287     public Collection<? extends Mutable<?, ?, ?>> mutableEffectiveSubstatements() {
288         if (effective instanceof ImmutableCollection) {
289             return effective;
290         }
291
292         return Collections.unmodifiableCollection(effective);
293     }
294
295     public void removeStatementsFromEffectiveSubstatements(
296             final Collection<? extends StmtContext<?, ?, ?>> substatements) {
297         if (!effective.isEmpty()) {
298             effective.removeAll(substatements);
299             shrinkEffective();
300         }
301     }
302
303     private void shrinkEffective() {
304         if (effective.isEmpty()) {
305             effective = ImmutableList.of();
306         }
307     }
308
309     public void removeStatementFromEffectiveSubstatements(final StatementDefinition statementDef) {
310         if (effective.isEmpty()) {
311             return;
312         }
313
314         final Iterator<? extends StmtContext<?, ?, ?>> iterator = effective.iterator();
315         while (iterator.hasNext()) {
316             final StmtContext<?, ?, ?> next = iterator.next();
317             if (statementDef.equals(next.getPublicDefinition())) {
318                 iterator.remove();
319             }
320         }
321
322         shrinkEffective();
323     }
324
325     /**
326      * Removes a statement context from the effective substatements based on its statement definition (i.e statement
327      * keyword) and raw (in String form) statement argument. The statement context is removed only if both statement
328      * definition and statement argument match with one of the effective substatements' statement definition
329      * and argument.
330      *
331      * <p>
332      * If the statementArg parameter is null, the statement context is removed based only on its statement definition.
333      *
334      * @param statementDef statement definition of the statement context to remove
335      * @param statementArg statement argument of the statement context to remove
336      */
337     public void removeStatementFromEffectiveSubstatements(final StatementDefinition statementDef,
338             final String statementArg) {
339         if (statementArg == null) {
340             removeStatementFromEffectiveSubstatements(statementDef);
341         }
342
343         if (effective.isEmpty()) {
344             return;
345         }
346
347         final Iterator<Mutable<?, ?, ?>> iterator = effective.iterator();
348         while (iterator.hasNext()) {
349             final Mutable<?, ?, ?> next = iterator.next();
350             if (statementDef.equals(next.getPublicDefinition()) && statementArg.equals(next.rawStatementArgument())) {
351                 iterator.remove();
352             }
353         }
354
355         shrinkEffective();
356     }
357
358     /**
359      * Adds an effective statement to collection of substatements.
360      *
361      * @param substatement substatement
362      * @throws IllegalStateException
363      *             if added in declared phase
364      * @throws NullPointerException
365      *             if statement parameter is null
366      */
367     public void addEffectiveSubstatement(final Mutable<?, ?, ?> substatement) {
368         beforeAddEffectiveStatement(1);
369         effective.add(substatement);
370     }
371
372     /**
373      * Adds an effective statement to collection of substatements.
374      *
375      * @param substatements substatements
376      * @throws IllegalStateException
377      *             if added in declared phase
378      * @throws NullPointerException
379      *             if statement parameter is null
380      */
381     public void addEffectiveSubstatements(final Collection<? extends Mutable<?, ?, ?>> substatements) {
382         if (substatements.isEmpty()) {
383             return;
384         }
385
386         substatements.forEach(Preconditions::checkNotNull);
387         beforeAddEffectiveStatement(substatements.size());
388         effective.addAll(substatements);
389     }
390
391     private void beforeAddEffectiveStatement(final int toAdd) {
392         final ModelProcessingPhase inProgressPhase = getRoot().getSourceContext().getInProgressPhase();
393         Preconditions.checkState(inProgressPhase == ModelProcessingPhase.FULL_DECLARATION
394                 || inProgressPhase == ModelProcessingPhase.EFFECTIVE_MODEL,
395                 "Effective statement cannot be added in declared phase at: %s", getStatementSourceReference());
396
397         if (effective.isEmpty()) {
398             effective = new ArrayList<>(toAdd);
399         }
400     }
401
402     /**
403      * Create a new substatement at the specified offset.
404      *
405      * @param offset Substatement offset
406      * @param def definition context
407      * @param ref source reference
408      * @param argument statement argument
409      * @return A new substatement
410      */
411     @SuppressWarnings("checkstyle:methodTypeParameterName")
412     public final <CA, CD extends DeclaredStatement<CA>, CE extends EffectiveStatement<CA, CD>>
413             StatementContextBase<CA, CD, CE> createSubstatement(final int offset,
414                     final StatementDefinitionContext<CA, CD, CE> def, final StatementSourceReference ref,
415                     final String argument) {
416         final ModelProcessingPhase inProgressPhase = getRoot().getSourceContext().getInProgressPhase();
417         Preconditions.checkState(inProgressPhase != ModelProcessingPhase.EFFECTIVE_MODEL,
418                 "Declared statement cannot be added in effective phase at: %s", getStatementSourceReference());
419
420         final Optional<StatementSupport<?, ?, ?>> implicitParent = definition.getImplicitParentFor(def.getPublicView());
421         if (implicitParent.isPresent()) {
422             return createImplicitParent(offset, implicitParent.get(), ref, argument).createSubstatement(offset, def,
423                     ref, argument);
424         }
425
426         final StatementContextBase<CA, CD, CE> ret = new SubstatementContext<>(this, def, ref, argument);
427         substatements = substatements.put(offset, ret);
428         def.onStatementAdded(ret);
429         return ret;
430     }
431
432     private StatementContextBase<?, ?, ?> createImplicitParent(final int offset,
433             final StatementSupport<?, ?, ?> implicitParent, final StatementSourceReference ref, final String argument) {
434         final StatementDefinitionContext<?, ?, ?> def = new StatementDefinitionContext<>(implicitParent);
435         return createSubstatement(offset, def, ImplicitSubstatement.of(ref), argument);
436     }
437
438     /**
439      * Lookup substatement by its offset in this statement.
440      *
441      * @param offset Substatement offset
442      * @return Substatement, or null if substatement does not exist.
443      */
444     final StatementContextBase<?, ?, ?> lookupSubstatement(final int offset) {
445         return substatements.get(offset);
446     }
447
448     @Override
449     public D buildDeclared() {
450         Preconditions.checkArgument(completedPhase == ModelProcessingPhase.FULL_DECLARATION
451                 || completedPhase == ModelProcessingPhase.EFFECTIVE_MODEL);
452         if (declaredInstance == null) {
453             declaredInstance = definition().getFactory().createDeclared(this);
454         }
455         return declaredInstance;
456     }
457
458     @Override
459     public E buildEffective() {
460         if (effectiveInstance == null) {
461             effectiveInstance = definition().getFactory().createEffective(this);
462         }
463         return effectiveInstance;
464     }
465
466     /**
467      * tries to execute current {@link ModelProcessingPhase} of source parsing.
468      *
469      * @param phase
470      *            to be executed (completed)
471      * @return if phase was successfully completed
472      * @throws SourceException
473      *             when an error occured in source parsing
474      */
475     boolean tryToCompletePhase(final ModelProcessingPhase phase) {
476
477         boolean finished = true;
478         final Collection<ContextMutation> openMutations = phaseMutation.get(phase);
479         if (!openMutations.isEmpty()) {
480             final Iterator<ContextMutation> it = openMutations.iterator();
481             while (it.hasNext()) {
482                 final ContextMutation current = it.next();
483                 if (current.isFinished()) {
484                     it.remove();
485                 } else {
486                     finished = false;
487                 }
488             }
489
490             if (openMutations.isEmpty()) {
491                 phaseMutation.removeAll(phase);
492                 if (phaseMutation.isEmpty()) {
493                     phaseMutation = ImmutableMultimap.of();
494                 }
495             }
496         }
497
498         for (final StatementContextBase<?, ?, ?> child : substatements.values()) {
499             finished &= child.tryToCompletePhase(phase);
500         }
501         for (final Mutable<?, ?, ?> child : effective) {
502             if (child instanceof StatementContextBase) {
503                 finished &= ((StatementContextBase<?, ?, ?>) child).tryToCompletePhase(phase);
504             }
505         }
506
507         if (finished) {
508             onPhaseCompleted(phase);
509             return true;
510         }
511         return false;
512     }
513
514     /**
515      * Occurs on end of {@link ModelProcessingPhase} of source parsing.
516      *
517      * @param phase
518      *            that was to be completed (finished)
519      * @throws SourceException
520      *             when an error occurred in source parsing
521      */
522     private void onPhaseCompleted(final ModelProcessingPhase phase) {
523         completedPhase = phase;
524
525         final Collection<OnPhaseFinished> listeners = phaseListeners.get(phase);
526         if (listeners.isEmpty()) {
527             return;
528         }
529
530         final Iterator<OnPhaseFinished> listener = listeners.iterator();
531         while (listener.hasNext()) {
532             final OnPhaseFinished next = listener.next();
533             if (next.phaseFinished(this, phase)) {
534                 listener.remove();
535             }
536         }
537
538         if (listeners.isEmpty()) {
539             phaseListeners.removeAll(phase);
540             if (phaseListeners.isEmpty()) {
541                 phaseListeners = ImmutableMultimap.of();
542             }
543         }
544     }
545
546     /**
547      * Ends declared section of current node.
548      */
549     void endDeclared(final StatementSourceReference ref, final ModelProcessingPhase phase) {
550         definition().onDeclarationFinished(this, phase);
551     }
552
553     /**
554      * Return the context in which this statement was defined.
555      *
556      * @return statement definition
557      */
558     protected final StatementDefinitionContext<A, D, E> definition() {
559         return definition;
560     }
561
562     @Override
563     protected void checkLocalNamespaceAllowed(final Class<? extends IdentifierNamespace<?, ?>> type) {
564         definition().checkNamespaceAllowed(type);
565     }
566
567     @Override
568     protected <K, V, N extends IdentifierNamespace<K, V>> void onNamespaceElementAdded(final Class<N> type, final K key,
569             final V value) {
570         // definition().onNamespaceElementAdded(this, type, key, value);
571     }
572
573     final <K, V, N extends IdentifierNamespace<K, V>> void onNamespaceItemAddedAction(final Class<N> type, final K key,
574             final OnNamespaceItemAdded listener) {
575         final Object potential = getFromNamespace(type, key);
576         if (potential != null) {
577             LOG.trace("Listener on {} key {} satisfied immediately", type, key);
578             listener.namespaceItemAdded(this, type, key, potential);
579             return;
580         }
581
582         getBehaviour(type).addListener(new KeyedValueAddedListener<K>(this, key) {
583             @Override
584             void onValueAdded(final Object value) {
585                 listener.namespaceItemAdded(StatementContextBase.this, type, key, value);
586             }
587         });
588     }
589
590     final <K, V, N extends IdentifierNamespace<K, V>> void onNamespaceItemAddedAction(final Class<N> type,
591             final ModelProcessingPhase phase, final NamespaceKeyCriterion<K> criterion,
592             final OnNamespaceItemAdded listener) {
593         final Optional<Entry<K, V>> existing = getFromNamespace(type, criterion);
594         if (existing.isPresent()) {
595             final Entry<K, V> entry = existing.get();
596             LOG.debug("Listener on {} criterion {} found a pre-existing match: {}", type, criterion, entry);
597             waitForPhase(entry.getValue(), type, phase, criterion, listener);
598             return;
599         }
600
601         final NamespaceBehaviourWithListeners<K, V, N> behaviour = getBehaviour(type);
602         behaviour.addListener(new PredicateValueAddedListener<K, V>(this) {
603             @Override
604             boolean onValueAdded(final K key, final V value) {
605                 if (criterion.match(key)) {
606                     LOG.debug("Listener on {} criterion {} matched added key {}", type, criterion, key);
607                     waitForPhase(value, type, phase, criterion, listener);
608                     return true;
609                 }
610
611                 return false;
612             }
613         });
614     }
615
616     final <K, V, N extends IdentifierNamespace<K, V>> void selectMatch(final Class<N> type,
617             final NamespaceKeyCriterion<K> criterion, final OnNamespaceItemAdded listener) {
618         final Optional<Entry<K, V>> optMatch = getFromNamespace(type, criterion);
619         Preconditions.checkState(optMatch.isPresent(),
620             "Failed to find a match for criterion %s in namespace %s node %s", criterion, type, this);
621         final Entry<K, V> match = optMatch.get();
622         listener.namespaceItemAdded(StatementContextBase.this, type, match.getKey(), match.getValue());
623     }
624
625     final <K, V, N extends IdentifierNamespace<K, V>> void waitForPhase(final Object value, final Class<N> type,
626             final ModelProcessingPhase phase, final NamespaceKeyCriterion<K> criterion,
627             final OnNamespaceItemAdded listener) {
628         ((StatementContextBase<?, ? ,?>) value).addPhaseCompletedListener(phase,
629             (context, completedPhase) -> {
630                 selectMatch(type, criterion, listener);
631                 return true;
632             });
633     }
634
635     private <K, V, N extends IdentifierNamespace<K, V>> NamespaceBehaviourWithListeners<K, V, N> getBehaviour(
636             final Class<N> type) {
637         final NamespaceBehaviour<K, V, N> behaviour = getBehaviourRegistry().getNamespaceBehaviour(type);
638         Preconditions.checkArgument(behaviour instanceof NamespaceBehaviourWithListeners,
639             "Namespace %s does not support listeners", type);
640
641         return (NamespaceBehaviourWithListeners<K, V, N>) behaviour;
642     }
643
644     /**
645      * See {@link StatementSupport#getPublicView()}.
646      */
647     @Nonnull
648     @Override
649     public StatementDefinition getPublicDefinition() {
650         return definition().getPublicView();
651     }
652
653     @Override
654     public ModelActionBuilder newInferenceAction(final ModelProcessingPhase phase) {
655         return getRoot().getSourceContext().newInferenceAction(phase);
656     }
657
658     private static <T> Multimap<ModelProcessingPhase, T> newMultimap() {
659         return Multimaps.newListMultimap(new EnumMap<>(ModelProcessingPhase.class), () -> new ArrayList<>(1));
660     }
661
662     /**
663      * Adds {@link OnPhaseFinished} listener for a {@link ModelProcessingPhase} end. If the base has already completed
664      * the listener is notified immediately.
665      *
666      * @param phase requested completion phase
667      * @param listener listener to invoke
668      * @throws NullPointerException if any of the arguments is null
669      */
670     void addPhaseCompletedListener(final ModelProcessingPhase phase, final OnPhaseFinished listener) {
671         Preconditions.checkNotNull(phase, "Statement context processing phase cannot be null at: %s",
672                 getStatementSourceReference());
673         Preconditions.checkNotNull(listener, "Statement context phase listener cannot be null at: %s",
674                 getStatementSourceReference());
675
676         ModelProcessingPhase finishedPhase = completedPhase;
677         while (finishedPhase != null) {
678             if (phase.equals(finishedPhase)) {
679                 listener.phaseFinished(this, finishedPhase);
680                 return;
681             }
682             finishedPhase = finishedPhase.getPreviousPhase();
683         }
684         if (phaseListeners.isEmpty()) {
685             phaseListeners = newMultimap();
686         }
687
688         phaseListeners.put(phase, listener);
689     }
690
691     /**
692      * Adds a {@link ContextMutation} to a {@link ModelProcessingPhase}.
693      *
694      * @throws IllegalStateException
695      *             when the mutation was registered after phase was completed
696      */
697     void addMutation(final ModelProcessingPhase phase, final ContextMutation mutation) {
698         ModelProcessingPhase finishedPhase = completedPhase;
699         while (finishedPhase != null) {
700             Preconditions.checkState(!phase.equals(finishedPhase),
701                 "Mutation registered after phase was completed at: %s", getStatementSourceReference());
702             finishedPhase = finishedPhase.getPreviousPhase();
703         }
704
705         if (phaseMutation.isEmpty()) {
706             phaseMutation = newMultimap();
707         }
708         phaseMutation.put(phase, mutation);
709     }
710
711     @Override
712     public <K, KT extends K, N extends StatementNamespace<K, ?, ?>> void addContext(final Class<N> namespace,
713             final KT key,final StmtContext<?, ?, ?> stmt) {
714         addContextToNamespace(namespace, key, stmt);
715     }
716
717     @Override
718     public <X, Y extends DeclaredStatement<X>, Z extends EffectiveStatement<X, Y>> Mutable<X, Y, Z> childCopyOf(
719             final StmtContext<X, Y, Z> stmt, final CopyType type, final QNameModule targetModule) {
720         Preconditions.checkState(stmt.getCompletedPhase() == ModelProcessingPhase.EFFECTIVE_MODEL,
721                 "Attempted to copy statement %s which has completed phase %s", stmt, stmt.getCompletedPhase());
722
723         Preconditions.checkArgument(stmt instanceof SubstatementContext, "Unsupported statement %s", stmt);
724
725         final SubstatementContext<X, Y, Z> original = (SubstatementContext<X, Y, Z>)stmt;
726         final SubstatementContext<X, Y, Z> copy = new SubstatementContext<>(original, this, type, targetModule);
727
728         original.definition().onStatementAdded(copy);
729         original.copyTo(copy, type, targetModule);
730
731         return copy;
732     }
733
734     final void copyTo(final StatementContextBase<?, ?, ?> target, final CopyType typeOfCopy,
735             @Nullable final QNameModule targetModule) {
736         final Collection<Mutable<?, ?, ?>> buffer = new ArrayList<>(substatements.size() + effective.size());
737
738         for (final Mutable<?, ?, ?> stmtContext : substatements.values()) {
739             if (stmtContext.isSupportedByFeatures()) {
740                 copySubstatement(stmtContext, target, typeOfCopy, targetModule, buffer);
741             }
742         }
743
744         for (final Mutable<?, ?, ?> stmtContext : effective) {
745             copySubstatement(stmtContext, target, typeOfCopy, targetModule, buffer);
746         }
747
748         target.addEffectiveSubstatements(buffer);
749     }
750
751     private void copySubstatement(final Mutable<?, ?, ?> stmtContext,  final Mutable<?, ?, ?> target,
752             final CopyType typeOfCopy, final QNameModule newQNameModule, final Collection<Mutable<?, ?, ?>> buffer) {
753         if (needToCopyByUses(stmtContext)) {
754             final Mutable<?, ?, ?> copy = target.childCopyOf(stmtContext, typeOfCopy, newQNameModule);
755             LOG.debug("Copying substatement {} for {} as", stmtContext, this, copy);
756             buffer.add(copy);
757         } else if (isReusedByUses(stmtContext)) {
758             LOG.debug("Reusing substatement {} for {}", stmtContext, this);
759             buffer.add(stmtContext);
760         } else {
761             LOG.debug("Skipping statement {}", stmtContext);
762         }
763     }
764
765     // FIXME: revise this, as it seems to be wrong
766     private static final Set<YangStmtMapping> NOCOPY_FROM_GROUPING_SET = ImmutableSet.of(
767         YangStmtMapping.DESCRIPTION,
768         YangStmtMapping.REFERENCE,
769         YangStmtMapping.STATUS);
770     private static final Set<YangStmtMapping> REUSED_DEF_SET = ImmutableSet.of(
771         YangStmtMapping.TYPE,
772         YangStmtMapping.TYPEDEF,
773         YangStmtMapping.USES);
774
775     private static boolean needToCopyByUses(final StmtContext<?, ?, ?> stmtContext) {
776         final StatementDefinition def = stmtContext.getPublicDefinition();
777         if (REUSED_DEF_SET.contains(def)) {
778             LOG.debug("Will reuse {} statement {}", def, stmtContext);
779             return false;
780         }
781         if (NOCOPY_FROM_GROUPING_SET.contains(def)) {
782             return !YangStmtMapping.GROUPING.equals(stmtContext.getParentContext().getPublicDefinition());
783         }
784
785         LOG.debug("Will copy {} statement {}", def, stmtContext);
786         return true;
787     }
788
789     private static boolean isReusedByUses(final StmtContext<?, ?, ?> stmtContext) {
790         return REUSED_DEF_SET.contains(stmtContext.getPublicDefinition());
791     }
792
793     @Override
794     public final String toString() {
795         return addToStringAttributes(MoreObjects.toStringHelper(this).omitNullValues()).toString();
796     }
797
798     protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
799         return toStringHelper.add("definition", definition).add("rawArgument", rawArgument);
800     }
801 }