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