BUG-5717: eliminate StmtContext.substatements()
[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.base.Throwables;
14 import com.google.common.collect.ImmutableCollection;
15 import com.google.common.collect.ImmutableList;
16 import com.google.common.collect.ImmutableMap;
17 import com.google.common.collect.ImmutableMultimap;
18 import com.google.common.collect.Multimap;
19 import com.google.common.collect.Multimaps;
20 import java.util.ArrayList;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.EnumMap;
24 import java.util.EventListener;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.Map;
28 import javax.annotation.Nonnull;
29 import org.opendaylight.yangtools.concepts.Identifiable;
30 import org.opendaylight.yangtools.yang.model.api.Rfc6020Mapping;
31 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
32 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
33 import org.opendaylight.yangtools.yang.model.api.meta.IdentifierNamespace;
34 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
35 import org.opendaylight.yangtools.yang.model.api.meta.StatementSource;
36 import org.opendaylight.yangtools.yang.parser.spi.meta.CopyHistory;
37 import org.opendaylight.yangtools.yang.parser.spi.meta.CopyType;
38 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder;
39 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase;
40 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour;
41 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour.StorageNodeType;
42 import org.opendaylight.yangtools.yang.parser.spi.meta.StatementNamespace;
43 import org.opendaylight.yangtools.yang.parser.spi.meta.StatementSupport;
44 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
45 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
46 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
47 import org.opendaylight.yangtools.yang.parser.stmt.reactor.NamespaceBehaviourWithListeners.ValueAddedListener;
48
49 public abstract class StatementContextBase<A, D extends DeclaredStatement<A>, E extends EffectiveStatement<A, D>>
50         extends NamespaceStorageSupport implements StmtContext.Mutable<A, D, E>, Identifiable<StatementIdentifier> {
51
52     @SuppressWarnings({ "rawtypes", "unchecked" })
53     private final class SubContextBuilder extends ContextBuilder {
54         SubContextBuilder(final StatementDefinitionContext def, final StatementSourceReference sourceRef) {
55             super(def, sourceRef);
56         }
57
58         @Override
59         public StatementContextBase build() {
60             StatementContextBase<?, ?, ?> potential = null;
61
62             final StatementDefinition stmtDef = getDefinition().getPublicView();
63             // FIXME: this is rather ugly. Rather than having an explicit blacklist, StatementDefinitionContext should
64             //        give us information whether we should really bother with the substatements map.
65             if (stmtDef != Rfc6020Mapping.AUGMENT && stmtDef != Rfc6020Mapping.DEVIATION
66                     && stmtDef != Rfc6020Mapping.IMPORT && stmtDef != Rfc6020Mapping.TYPE) {
67                 potential = substatements.get(createIdentifier());
68             }
69             if (potential == null) {
70                 potential = new SubstatementContext(StatementContextBase.this, this);
71                 if (substatements.isEmpty()) {
72                     substatements = new HashMap<>(1);
73                 }
74                 substatements.put(createIdentifier(), potential);
75                 getDefinition().onStatementAdded(potential);
76             }
77             potential.resetLists();
78             switch (this.getStamementSource().getStatementSource()) {
79             case DECLARATION:
80                 addDeclaredSubstatement(potential);
81                 break;
82             case CONTEXT:
83                 addEffectiveSubstatement(potential);
84                 break;
85             }
86             return potential;
87         }
88     }
89
90     /**
91      * event listener when an item is added to model namespace
92      */
93     interface OnNamespaceItemAdded extends EventListener {
94         /**
95          * @throws SourceException
96          */
97         void namespaceItemAdded(StatementContextBase<?, ?, ?> context, Class<?> namespace, Object key, Object value);
98     }
99
100     /**
101      * event listener when a parsing {@link ModelProcessingPhase} is completed
102      */
103     interface OnPhaseFinished extends EventListener {
104         /**
105          * @throws SourceException
106          */
107         boolean phaseFinished(StatementContextBase<?, ?, ?> context, ModelProcessingPhase phase);
108     }
109
110     /**
111      * interface for all mutations within an {@link ModelActionBuilder.InferenceAction}
112      */
113     interface ContextMutation {
114
115         boolean isFinished();
116     }
117
118     private final StatementDefinitionContext<A, D, E> definition;
119     private final StatementIdentifier identifier;
120     private final StatementSourceReference statementDeclSource;
121
122     private Multimap<ModelProcessingPhase, OnPhaseFinished> phaseListeners = ImmutableMultimap.of();
123     private Multimap<ModelProcessingPhase, ContextMutation> phaseMutation = ImmutableMultimap.of();
124     private Map<StatementIdentifier, StatementContextBase<?, ?, ?>> substatements = ImmutableMap.of();
125     private Collection<StatementContextBase<?, ?, ?>> declared = ImmutableList.of();
126     private Collection<StatementContextBase<?, ?, ?>> effective = ImmutableList.of();
127     private Collection<StatementContextBase<?, ?, ?>> effectOfStatement = ImmutableList.of();
128
129     private SupportedByFeatures supportedByFeatures = SupportedByFeatures.UNDEFINED;
130     private CopyHistory copyHistory = CopyHistory.original();
131     private boolean isSupportedToBuildEffective = true;
132     private ModelProcessingPhase completedPhase = null;
133     private StatementContextBase<?, ?, ?> originalCtx;
134     private D declaredInstance;
135     private E effectiveInstance;
136     private int order = 0;
137
138     StatementContextBase(@Nonnull final ContextBuilder<A, D, E> builder) {
139         this.definition = builder.getDefinition();
140         this.identifier = builder.createIdentifier();
141         this.statementDeclSource = builder.getStamementSource();
142     }
143
144     StatementContextBase(final StatementContextBase<A, D, E> original) {
145         this.definition = Preconditions.checkNotNull(original.definition,
146                 "Statement context definition cannot be null copying from: %s", original.getStatementSourceReference());
147         this.identifier = Preconditions.checkNotNull(original.identifier,
148                 "Statement context identifier cannot be null copying from: %s", original.getStatementSourceReference());
149         this.statementDeclSource = Preconditions.checkNotNull(original.statementDeclSource,
150                 "Statement context statementDeclSource cannot be null copying from: %s",
151                 original.getStatementSourceReference());
152     }
153
154     @Override
155     public Collection<StatementContextBase<?, ?, ?>> getEffectOfStatement() {
156         return effectOfStatement;
157     }
158
159     @Override
160     public void addAsEffectOfStatement(final StatementContextBase<?, ?, ?> ctx) {
161         if (effectOfStatement.isEmpty()) {
162             effectOfStatement = new ArrayList<>(1);
163         }
164         effectOfStatement.add(ctx);
165     }
166
167     @Override
168     public void addAsEffectOfStatement(final Collection<StatementContextBase<?, ?, ?>> ctxs) {
169         if (ctxs.isEmpty()) {
170             return;
171         }
172
173         if (effectOfStatement.isEmpty()) {
174             effectOfStatement = new ArrayList<>(ctxs.size());
175         }
176         effectOfStatement.addAll(ctxs);
177     }
178
179     @Override
180     public SupportedByFeatures getSupportedByFeatures() {
181         return supportedByFeatures;
182     }
183
184     @Override
185     public void setSupportedByFeatures(final boolean isSupported) {
186         this.supportedByFeatures = isSupported ? SupportedByFeatures.SUPPORTED : SupportedByFeatures.NOT_SUPPORTED;
187     }
188
189     @Override
190     public boolean isSupportedToBuildEffective() {
191         return isSupportedToBuildEffective;
192     }
193
194     @Override
195     public void setIsSupportedToBuildEffective(final boolean isSupportedToBuildEffective) {
196         this.isSupportedToBuildEffective = isSupportedToBuildEffective;
197     }
198
199     @Override
200     public CopyHistory getCopyHistory() {
201         return copyHistory;
202     }
203
204     @Override
205     public void appendCopyHistory(final CopyType typeOfCopy, final CopyHistory toAppend) {
206         copyHistory = copyHistory.append(typeOfCopy, toAppend);
207     }
208
209     @Override
210     public StatementContextBase<?, ?, ?> getOriginalCtx() {
211         return originalCtx;
212     }
213
214     @Override
215     public void setOriginalCtx(final StatementContextBase<?, ?, ?> originalCtx) {
216         this.originalCtx = originalCtx;
217     }
218
219     @Override
220     public void setOrder(final int order) {
221         this.order = order;
222     }
223
224     @Override
225     public int getOrder() {
226         return order;
227     }
228
229     @Override
230     public ModelProcessingPhase getCompletedPhase() {
231         return completedPhase;
232     }
233
234     @Override
235     public void setCompletedPhase(final ModelProcessingPhase completedPhase) {
236         this.completedPhase = completedPhase;
237     }
238
239     /**
240      * @return context of parent of statement
241      */
242     @Override
243     public abstract StatementContextBase<?, ?, ?> getParentContext();
244
245     /**
246      * @return root context of statement
247      */
248     @Override
249     public abstract RootStatementContext<?, ?, ?> getRoot();
250
251     /**
252      * @return statement identifier
253      */
254     @Override
255     public StatementIdentifier getIdentifier() {
256         return identifier;
257     }
258
259     /**
260      * @return origin of statement
261      */
262     @Override
263     public StatementSource getStatementSource() {
264         return statementDeclSource.getStatementSource();
265     }
266
267     /**
268      * @return reference of statement source
269      */
270     @Override
271     public StatementSourceReference getStatementSourceReference() {
272         return statementDeclSource;
273     }
274
275     /**
276      * @return raw statement argument string
277      */
278     @Override
279     public String rawStatementArgument() {
280         return identifier.getArgument();
281     }
282
283     private static final <T> Collection<T> maybeWrap(final Collection<T> input) {
284         if (input instanceof ImmutableCollection) {
285             return input;
286         }
287
288         return Collections.unmodifiableCollection(input);
289     }
290
291     @Override
292     public Collection<StatementContextBase<?, ?, ?>> declaredSubstatements() {
293         return maybeWrap(declared);
294     }
295
296     @Override
297     public Collection<StatementContextBase<?, ?, ?>> effectiveSubstatements() {
298         return maybeWrap(effective);
299     }
300
301     public void removeStatementsFromEffectiveSubstatements(final Collection<StatementContextBase<?, ?, ?>> substatements) {
302         if (!effective.isEmpty()) {
303             effective.removeAll(substatements);
304             shrinkEffective();
305         }
306     }
307
308     private void shrinkEffective() {
309         if (effective.isEmpty()) {
310             effective = ImmutableList.of();
311         }
312     }
313
314     public void removeStatementFromEffectiveSubstatements(final StatementDefinition refineSubstatementDef) {
315         if (effective.isEmpty()) {
316             return;
317         }
318
319         final Iterator<StatementContextBase<?, ?, ?>> iterator = effective.iterator();
320         while (iterator.hasNext()) {
321             final StatementContextBase<?, ?, ?> next = iterator.next();
322             if (next.getPublicDefinition().equals(refineSubstatementDef)) {
323                 iterator.remove();
324             }
325         }
326
327         shrinkEffective();
328     }
329
330     /**
331      * adds effective statement to collection of substatements
332      *
333      * @param substatement substatement
334      * @throws IllegalStateException
335      *             if added in declared phase
336      * @throws NullPointerException
337      *             if statement parameter is null
338      */
339     public void addEffectiveSubstatement(final StatementContextBase<?, ?, ?> substatement) {
340         Preconditions.checkNotNull(substatement, "StatementContextBase effective substatement cannot be null at: %s",
341             getStatementSourceReference());
342         beforeAddEffectiveStatement(1);
343         effective.add(substatement);
344     }
345
346     /**
347      * adds effective statement to collection of substatements
348      *
349      * @param substatements substatements
350      * @throws IllegalStateException
351      *             if added in declared phase
352      * @throws NullPointerException
353      *             if statement parameter is null
354      */
355     public void addEffectiveSubstatements(final Collection<StatementContextBase<?, ?, ?>> substatements) {
356         if (substatements.isEmpty()) {
357             return;
358         }
359
360         substatements.forEach(Preconditions::checkNotNull);
361         beforeAddEffectiveStatement(substatements.size());
362         effective.addAll(substatements);
363     }
364
365     private void beforeAddEffectiveStatement(final int toAdd) {
366         final ModelProcessingPhase inProgressPhase = getRoot().getSourceContext().getInProgressPhase();
367         Preconditions.checkState(inProgressPhase == ModelProcessingPhase.FULL_DECLARATION
368                 || inProgressPhase == ModelProcessingPhase.EFFECTIVE_MODEL,
369                 "Effective statement cannot be added in declared phase at: %s", getStatementSourceReference());
370
371         if (effective.isEmpty()) {
372             effective = new ArrayList<>(toAdd);
373         }
374     }
375
376     /**
377      * adds declared statement to collection of substatements
378      *
379      * @param substatement substatement
380      * @throws IllegalStateException
381      *             if added in effective phase
382      * @throws NullPointerException
383      *             if statement parameter is null
384      */
385     public void addDeclaredSubstatement(final StatementContextBase<?, ?, ?> substatement) {
386
387         final ModelProcessingPhase inProgressPhase = getRoot().getSourceContext().getInProgressPhase();
388         Preconditions.checkState(inProgressPhase != ModelProcessingPhase.EFFECTIVE_MODEL,
389                 "Declared statement cannot be added in effective phase at: %s", getStatementSourceReference());
390
391         if (declared.isEmpty()) {
392             declared = new ArrayList<>(1);
393         }
394         declared.add(Preconditions.checkNotNull(substatement,
395                 "StatementContextBase declared substatement cannot be null at: %s", getStatementSourceReference()));
396     }
397
398     /**
399      * builds a new substatement from statement definition context and statement source reference
400      *
401      * @param def definition context
402      * @param ref source reference
403      *
404      * @return instance of ContextBuilder
405      */
406     public ContextBuilder<?, ?, ?> substatementBuilder(final StatementDefinitionContext<?, ?, ?> def,
407             final StatementSourceReference ref) {
408         return new SubContextBuilder(def, ref);
409     }
410
411     /**
412      * @return local namespace behaviour type {@link NamespaceBehaviour}
413      */
414     @Override
415     public StorageNodeType getStorageNodeType() {
416         return StorageNodeType.STATEMENT_LOCAL;
417     }
418
419     /**
420      * builds {@link DeclaredStatement} for statement context
421      */
422     @Override
423     public D buildDeclared() {
424         Preconditions.checkArgument(completedPhase == ModelProcessingPhase.FULL_DECLARATION
425                 || completedPhase == ModelProcessingPhase.EFFECTIVE_MODEL);
426         if (declaredInstance == null) {
427             declaredInstance = definition().getFactory().createDeclared(this);
428         }
429         return declaredInstance;
430     }
431
432     /**
433      * builds {@link EffectiveStatement} for statement context
434      */
435     @Override
436     public E buildEffective() {
437         if (effectiveInstance == null) {
438             effectiveInstance = definition().getFactory().createEffective(this);
439         }
440         return effectiveInstance;
441     }
442
443     /**
444      * clears collection of declared substatements
445      *
446      * @throws IllegalStateException
447      *             if invoked in effective build phase
448      */
449     void resetLists() {
450
451         final SourceSpecificContext sourceContext = getRoot().getSourceContext();
452         Preconditions.checkState(sourceContext.getInProgressPhase() != ModelProcessingPhase.EFFECTIVE_MODEL,
453                 "Declared statements list cannot be cleared in effective phase at: %s", getStatementSourceReference());
454
455         declared = ImmutableList.of();
456     }
457
458     /**
459      * tries to execute current {@link ModelProcessingPhase} of source parsing
460      *
461      * @param phase
462      *            to be executed (completed)
463      * @return if phase was successfully completed
464      * @throws SourceException
465      *             when an error occured in source parsing
466      */
467     boolean tryToCompletePhase(final ModelProcessingPhase phase) {
468
469         boolean finished = true;
470         final Collection<ContextMutation> openMutations = phaseMutation.get(phase);
471         if (!openMutations.isEmpty()) {
472             final Iterator<ContextMutation> it = openMutations.iterator();
473             while (it.hasNext()) {
474                 final ContextMutation current = it.next();
475                 if (current.isFinished()) {
476                     it.remove();
477                 } else {
478                     finished = false;
479                 }
480             }
481
482             if (openMutations.isEmpty()) {
483                 phaseMutation.removeAll(phase);
484                 if (phaseMutation.isEmpty()) {
485                     phaseMutation = ImmutableMultimap.of();
486                 }
487             }
488         }
489
490         for (final StatementContextBase<?, ?, ?> child : declared) {
491             finished &= child.tryToCompletePhase(phase);
492         }
493         for (final StatementContextBase<?, ?, ?> child : effective) {
494             finished &= child.tryToCompletePhase(phase);
495         }
496
497         if (finished) {
498             onPhaseCompleted(phase);
499             return true;
500         }
501         return false;
502     }
503
504     /**
505      * occurs on end of {@link ModelProcessingPhase} of source parsing
506      *
507      * @param phase
508      *            that was to be completed (finished)
509      * @throws SourceException
510      *             when an error occured in source parsing
511      */
512     private void onPhaseCompleted(final ModelProcessingPhase phase) {
513         completedPhase = phase;
514
515         final Collection<OnPhaseFinished> listeners = phaseListeners.get(phase);
516         if (listeners.isEmpty()) {
517             return;
518         }
519
520         final Iterator<OnPhaseFinished> listener = listeners.iterator();
521         while (listener.hasNext()) {
522             final OnPhaseFinished next = listener.next();
523             if (next.phaseFinished(this, phase)) {
524                 listener.remove();
525             }
526         }
527
528         if (listeners.isEmpty()) {
529             phaseListeners.removeAll(phase);
530             if (phaseListeners.isEmpty()) {
531                 phaseListeners = ImmutableMultimap.of();
532             }
533         }
534     }
535
536     /**
537      * Ends declared section of current node.
538      *
539      * @param ref
540      * @throws SourceException
541      */
542     void endDeclared(final StatementSourceReference ref, final ModelProcessingPhase phase) {
543         definition().onDeclarationFinished(this, phase);
544     }
545
546     /**
547      * @return statement definition
548      */
549     protected final StatementDefinitionContext<A, D, E> definition() {
550         return definition;
551     }
552
553     @Override
554     protected void checkLocalNamespaceAllowed(final Class<? extends IdentifierNamespace<?, ?>> type) {
555         definition().checkNamespaceAllowed(type);
556     }
557
558     /**
559      * occurs when an item is added to model namespace
560      *
561      * @throws SourceException instance of SourceException
562      */
563     @Override
564     protected <K, V, N extends IdentifierNamespace<K, V>> void onNamespaceElementAdded(final Class<N> type, final K key, final V value) {
565         // definition().onNamespaceElementAdded(this, type, key, value);
566     }
567
568     <K, V, N extends IdentifierNamespace<K, V>> void onNamespaceItemAddedAction(final Class<N> type, final K key,
569             final OnNamespaceItemAdded listener) throws SourceException {
570         final Object potential = getFromNamespace(type, key);
571         if (potential != null) {
572             listener.namespaceItemAdded(this, type, key, potential);
573             return;
574         }
575         final NamespaceBehaviour<K, V, N> behaviour = getBehaviourRegistry().getNamespaceBehaviour(type);
576         if (behaviour instanceof NamespaceBehaviourWithListeners) {
577             final NamespaceBehaviourWithListeners<K, V, N> casted = (NamespaceBehaviourWithListeners<K, V, N>) behaviour;
578             casted.addValueListener(new ValueAddedListener<K>(this, key) {
579                 @Override
580                 void onValueAdded(final Object key, final Object value) {
581                     try {
582                         listener.namespaceItemAdded(StatementContextBase.this, type, key, value);
583                     } catch (final SourceException e) {
584                         throw Throwables.propagate(e);
585                     }
586                 }
587             });
588         }
589     }
590
591     /**
592      * @see StatementSupport#getPublicView()
593      */
594     @Override
595     public StatementDefinition getPublicDefinition() {
596         return definition().getPublicView();
597     }
598
599     /**
600      * @return new {@link ModelActionBuilder} for the phase
601      */
602     @Override
603     public ModelActionBuilder newInferenceAction(final ModelProcessingPhase phase) {
604         return getRoot().getSourceContext().newInferenceAction(phase);
605     }
606
607     private static <T> Multimap<ModelProcessingPhase, T> newMultimap() {
608         return Multimaps.newListMultimap(new EnumMap<>(ModelProcessingPhase.class), () -> new ArrayList<>(1));
609     }
610
611     /**
612      * adds {@link OnPhaseFinished} listener for a {@link ModelProcessingPhase} end
613      *
614      * @throws SourceException
615      */
616     void addPhaseCompletedListener(final ModelProcessingPhase phase, final OnPhaseFinished listener) {
617
618         Preconditions.checkNotNull(phase, "Statement context processing phase cannot be null at: %s",
619                 getStatementSourceReference());
620         Preconditions.checkNotNull(listener, "Statement context phase listener cannot be null at: %s",
621                 getStatementSourceReference());
622
623         ModelProcessingPhase finishedPhase = completedPhase;
624         while (finishedPhase != null) {
625             if (phase.equals(finishedPhase)) {
626                 listener.phaseFinished(this, finishedPhase);
627                 return;
628             }
629             finishedPhase = finishedPhase.getPreviousPhase();
630         }
631         if (phaseListeners.isEmpty()) {
632             phaseListeners = newMultimap();
633         }
634
635         phaseListeners.put(phase, listener);
636     }
637
638     /**
639      * adds {@link ContextMutation} to {@link ModelProcessingPhase}
640      *
641      * @throws IllegalStateException
642      *             when the mutation was registered after phase was completed
643      */
644     void addMutation(final ModelProcessingPhase phase, final ContextMutation mutation) {
645         ModelProcessingPhase finishedPhase = completedPhase;
646         while (finishedPhase != null) {
647             if (phase.equals(finishedPhase)) {
648                 throw new IllegalStateException("Mutation registered after phase was completed at: "  +
649                         getStatementSourceReference());
650             }
651             finishedPhase = finishedPhase.getPreviousPhase();
652         }
653
654         if (phaseMutation.isEmpty()) {
655             phaseMutation = newMultimap();
656         }
657         phaseMutation.put(phase, mutation);
658     }
659
660     /**
661      * adds statement to namespace map with the key
662      *
663      * @param namespace
664      *            {@link StatementNamespace} child that determines namespace to be added to
665      * @param key
666      *            of type according to namespace class specification
667      * @param stmt
668      *            to be added to namespace map
669      */
670     @Override
671     public <K, KT extends K, N extends StatementNamespace<K, ?, ?>> void addContext(final Class<N> namespace, final KT key,
672             final StmtContext<?, ?, ?> stmt) {
673         addContextToNamespace(namespace, key, stmt);
674     }
675
676     @Override
677     public final String toString() {
678         return addToStringAttributes(MoreObjects.toStringHelper(this).omitNullValues()).toString();
679     }
680
681     protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
682         return toStringHelper.add("definition", definition).add("id", identifier);
683     }
684 }