Split out NamespaceStorage(Node/Type)
[yangtools.git] / parser / yang-parser-reactor / src / main / java / org / opendaylight / yangtools / yang / parser / stmt / reactor / SourceSpecificContext.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 static com.google.common.base.Preconditions.checkState;
11 import static com.google.common.base.Verify.verify;
12 import static com.google.common.base.Verify.verifyNotNull;
13 import static java.util.Objects.requireNonNull;
14
15 import com.google.common.collect.HashMultimap;
16 import com.google.common.collect.ImmutableList;
17 import com.google.common.collect.Multimap;
18 import java.util.ArrayList;
19 import java.util.Collection;
20 import java.util.Iterator;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Map.Entry;
24 import java.util.Objects;
25 import java.util.Optional;
26 import org.eclipse.jdt.annotation.NonNull;
27 import org.opendaylight.yangtools.concepts.Mutable;
28 import org.opendaylight.yangtools.yang.common.QName;
29 import org.opendaylight.yangtools.yang.common.QNameModule;
30 import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified;
31 import org.opendaylight.yangtools.yang.common.YangVersion;
32 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
33 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
34 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
35 import org.opendaylight.yangtools.yang.parser.spi.ParserNamespaces;
36 import org.opendaylight.yangtools.yang.parser.spi.meta.InferenceException;
37 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelActionBuilder;
38 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase;
39 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour;
40 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceStorage;
41 import org.opendaylight.yangtools.yang.parser.spi.meta.ParserNamespace;
42 import org.opendaylight.yangtools.yang.parser.spi.meta.StatementDefinitions;
43 import org.opendaylight.yangtools.yang.parser.spi.meta.StatementSupport;
44 import org.opendaylight.yangtools.yang.parser.spi.meta.StatementSupportBundle;
45 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
46 import org.opendaylight.yangtools.yang.parser.spi.source.PrefixResolver;
47 import org.opendaylight.yangtools.yang.parser.spi.source.QNameToStatementDefinition;
48 import org.opendaylight.yangtools.yang.parser.spi.source.QNameToStatementDefinitionMap;
49 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
50 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
51 import org.opendaylight.yangtools.yang.parser.spi.source.StatementStreamSource;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 final class SourceSpecificContext implements NamespaceStorage, Mutable {
56     enum PhaseCompletionProgress {
57         NO_PROGRESS,
58         PROGRESS,
59         FINISHED
60     }
61
62     private static final class SupportedStatements extends NamespaceBehaviour<QName, StatementSupport<?, ?, ?>> {
63         private final QNameToStatementDefinitionMap statementDefinitions;
64
65         SupportedStatements(final QNameToStatementDefinitionMap statementDefinitions) {
66             super(StatementSupport.NAMESPACE);
67             this.statementDefinitions = requireNonNull(statementDefinitions);
68         }
69
70         @Override
71         public StatementSupport<?, ?, ?> getFrom(final NamespaceStorage storage, final QName key) {
72             return statementDefinitions.getSupport(key);
73         }
74
75         @Override
76         public Map<QName, StatementSupport<?, ?, ?>> getAllFrom(final NamespaceStorage storage) {
77             throw uoe();
78         }
79
80         @Override
81         public void addTo(final NamespaceStorage storage, final QName key, final StatementSupport<?, ?, ?> value) {
82             throw uoe();
83         }
84
85         private static UnsupportedOperationException uoe() {
86             return new UnsupportedOperationException("StatementSupportNamespace is immutable");
87         }
88     }
89
90     private static final Logger LOG = LoggerFactory.getLogger(SourceSpecificContext.class);
91
92     // TODO: consider keying by Byte equivalent of ExecutionOrder
93     private final Multimap<ModelProcessingPhase, ModifierImpl> modifiers = HashMultimap.create();
94     private final QNameToStatementDefinitionMap qnameToStmtDefMap = new QNameToStatementDefinitionMap();
95     private final @NonNull SupportedStatements statementSupports = new SupportedStatements(qnameToStmtDefMap);
96     private final HashMapPrefixResolver prefixToModuleMap = new HashMapPrefixResolver();
97     private final @NonNull BuildGlobalContext globalContext;
98
99     // Freed as soon as we complete ModelProcessingPhase.EFFECTIVE_MODEL
100     private StatementStreamSource source;
101
102     /*
103      * "imported" namespaces in this source -- this points to RootStatementContexts of
104      * - modules imported via 'import' statement
105      * - parent module, declared via 'belongs-to' statement
106      */
107     private List<RootStatementContext<?, ?, ?>> importedNamespaces = ImmutableList.of();
108     private RootStatementContext<?, ?, ?> root;
109     // TODO: consider using ExecutionOrder byte for these two
110     private ModelProcessingPhase finishedPhase = ModelProcessingPhase.INIT;
111     private ModelProcessingPhase inProgressPhase;
112
113     // If not null, do not add anything to modifiers, but record it here.
114     private List<Entry<ModelProcessingPhase, ModifierImpl>> delayedModifiers;
115
116     SourceSpecificContext(final BuildGlobalContext globalContext, final StatementStreamSource source) {
117         this.globalContext = requireNonNull(globalContext);
118         this.source = requireNonNull(source);
119     }
120
121     @NonNull BuildGlobalContext globalContext() {
122         return globalContext;
123     }
124
125     ModelProcessingPhase getInProgressPhase() {
126         return inProgressPhase;
127     }
128
129     AbstractResumedStatement<?, ?, ?> createDeclaredChild(final AbstractResumedStatement<?, ?, ?> current,
130             final int childId, final QName name, final String argument, final StatementSourceReference ref) {
131         StatementDefinitionContext<?, ?, ?> def = globalContext.getStatementDefinition(getRootVersion(), name);
132         if (def == null) {
133             def = globalContext.getModelDefinedStatementDefinition(name);
134             if (def == null) {
135                 final StatementSupport<?, ?, ?> extension = qnameToStmtDefMap.getSupport(name);
136                 if (extension != null) {
137                     def = new StatementDefinitionContext<>(extension);
138                     globalContext.putModelDefinedStatementDefinition(name, def);
139                 }
140             }
141         } else if (current != null) {
142             def = current.definition().overrideDefinition(def);
143         }
144
145         if (InferenceException.throwIfNull(def, ref, "Statement %s does not have type mapping defined.", name)
146                 .getArgumentDefinition().isPresent()) {
147             SourceException.throwIfNull(argument, ref, "Statement %s requires an argument", name);
148         } else {
149             SourceException.throwIf(argument != null, ref, "Statement %s does not take argument", name);
150         }
151
152         /*
153          * If the current statement definition has argument specific
154          * sub-definitions, get argument specific sub-definition based on given
155          * argument (e.g. type statement need to be specialized based on its
156          * argument).
157          */
158         if (def.hasArgumentSpecificSubDefinitions()) {
159             def = def.getSubDefinitionSpecificForArgument(argument);
160         }
161
162         if (current != null) {
163             return current.createSubstatement(childId, def, ref, argument);
164         }
165
166         /*
167          * If root is null or root version is other than default,
168          * we need to create new root.
169          */
170         if (root == null) {
171             root = new RootStatementContext<>(this, def, ref, argument);
172         } else if (!RootStatementContext.DEFAULT_VERSION.equals(root.yangVersion())
173                 && inProgressPhase == ModelProcessingPhase.SOURCE_LINKAGE) {
174             root = new RootStatementContext<>(this, def, ref, argument, root.yangVersion(),
175                     root.getRootIdentifier());
176         } else {
177             final QName rootStatement = root.definition().getStatementName();
178             final String rootArgument = root.rawArgument();
179
180             checkState(Objects.equals(def.getStatementName(), rootStatement) && Objects.equals(argument, rootArgument),
181                 "Root statement was already defined as '%s %s'.", rootStatement, rootArgument);
182         }
183         return root;
184     }
185
186     @NonNull SourceIdentifier identifySource() {
187         final var arg = root.getArgument();
188         verify(arg instanceof Unqualified, "Unexpected argument %s", arg);
189         final var unqualified = (Unqualified) arg;
190
191         final var module = root.namespaceItem(ParserNamespaces.MODULECTX_TO_QNAME, root);
192         if (module != null) {
193             // creates SourceIdentifier for a module
194             return new SourceIdentifier(unqualified, module.getRevision().orElse(null));
195         }
196
197         // creates SourceIdentifier for a submodule
198         return new SourceIdentifier(unqualified,
199             StmtContextUtils.getLatestRevision(root.declaredSubstatements()).orElse(null));
200     }
201
202     @NonNull DeclaredStatement<?> declaredRoot() {
203         return root.declared();
204     }
205
206     @NonNull EffectiveStatement<?, ?> effectiveRoot() {
207         return root.buildEffective();
208     }
209
210     /**
211      * Return version of root statement context.
212      *
213      * @return version of root statement context
214      */
215     private YangVersion getRootVersion() {
216         return root != null ? root.yangVersion() : RootStatementContext.DEFAULT_VERSION;
217     }
218
219     void startPhase(final ModelProcessingPhase phase) {
220         final ModelProcessingPhase previousPhase = phase.getPreviousPhase();
221         verify(Objects.equals(previousPhase, finishedPhase),
222             "Phase sequencing violation: previous phase should be %s, source %s has %s", previousPhase, source,
223             finishedPhase);
224
225         final Collection<ModifierImpl> previousModifiers = modifiers.get(previousPhase);
226         checkState(previousModifiers.isEmpty(), "Previous phase %s has unresolved modifiers %s in source %s",
227             previousPhase, previousModifiers, source);
228
229         inProgressPhase = phase;
230         LOG.debug("Source {} started phase {}", source, phase);
231     }
232
233     private void updateImportedNamespaces(final ParserNamespace<?, ?> type, final Object value) {
234         if (ParserNamespaces.BELONGSTO_PREFIX_TO_MODULECTX.equals(type)
235             || ParserNamespaces.IMPORTED_MODULE.equals(type)) {
236             verify(value instanceof RootStatementContext, "Unexpected imported value %s", value);
237
238             if (importedNamespaces.isEmpty()) {
239                 importedNamespaces = new ArrayList<>(1);
240             }
241             importedNamespaces.add((RootStatementContext<?, ?, ?>) value);
242         }
243     }
244
245     @Override
246     public <K, V> V putToLocalStorage(final ParserNamespace<K, V> type, final K key, final V value) {
247         // RootStatementContext takes care of IncludedModuleContext and the rest...
248         final V ret = root.putToLocalStorage(type, key, value);
249         // FIXME: what about duplicates?
250         updateImportedNamespaces(type, value);
251         return ret;
252     }
253
254     @Override
255     public <K, V> V putToLocalStorageIfAbsent(final ParserNamespace<K, V> type, final K key, final V value) {
256         // RootStatementContext takes care of IncludedModuleContext and the rest...
257         final V ret = root.putToLocalStorageIfAbsent(type, key, value);
258         if (ret == null) {
259             updateImportedNamespaces(type, value);
260         }
261         return ret;
262     }
263
264     @Override
265     public StorageType getStorageType() {
266         return StorageType.SOURCE_LOCAL_SPECIAL;
267     }
268
269     @Override
270     public <K, V> V getFromLocalStorage(final ParserNamespace<K, V> type, final K key) {
271         final V potentialLocal = root.getFromLocalStorage(type, key);
272         if (potentialLocal != null) {
273             return potentialLocal;
274         }
275
276         for (final NamespaceStorage importedSource : importedNamespaces) {
277             final V potential = importedSource.getFromLocalStorage(type, key);
278             if (potential != null) {
279                 return potential;
280             }
281         }
282         return null;
283     }
284
285     @Override
286     public <K, V> Map<K, V> getAllFromLocalStorage(final ParserNamespace<K, V> type) {
287         final Map<K, V> potentialLocal = root.getAllFromLocalStorage(type);
288         if (potentialLocal != null) {
289             return potentialLocal;
290         }
291
292         for (final NamespaceStorage importedSource : importedNamespaces) {
293             final Map<K, V> potential = importedSource.getAllFromLocalStorage(type);
294
295             if (potential != null) {
296                 return potential;
297             }
298         }
299         return null;
300     }
301
302     <K, V> NamespaceBehaviour<K, V> getNamespaceBehaviour(final ParserNamespace<K, V> type) {
303         if (StatementSupport.NAMESPACE.equals(type)) {
304             @SuppressWarnings("unchecked")
305             final var ret = (NamespaceBehaviour<K, V>) statementSupports;
306             return ret;
307         }
308         return globalContext.getNamespaceBehaviour(type);
309     }
310
311     @Override
312     public BuildGlobalContext getParentStorage() {
313         return globalContext;
314     }
315
316     PhaseCompletionProgress tryToCompletePhase(final byte executionOrder) {
317         final ModelProcessingPhase phase = verifyNotNull(ModelProcessingPhase.ofExecutionOrder(executionOrder));
318         final Collection<ModifierImpl> currentPhaseModifiers = modifiers.get(phase);
319
320         boolean hasProgressed = tryToProgress(currentPhaseModifiers);
321         final boolean phaseCompleted = requireNonNull(root, "Malformed source. Valid root element is missing.")
322                 .tryToCompletePhase(executionOrder);
323
324         hasProgressed |= tryToProgress(currentPhaseModifiers);
325
326         // TODO: use executionOrder instead?
327         if (phaseCompleted && currentPhaseModifiers.isEmpty()) {
328             finishedPhase = phase;
329             LOG.debug("Source {} finished phase {}", source, phase);
330             if (phase == ModelProcessingPhase.EFFECTIVE_MODEL) {
331                 // We have the effective model acquired, which is the final phase of source interaction.
332                 LOG.trace("Releasing source {}", source);
333                 source = null;
334             }
335             return PhaseCompletionProgress.FINISHED;
336         }
337
338         return hasProgressed ? PhaseCompletionProgress.PROGRESS : PhaseCompletionProgress.NO_PROGRESS;
339     }
340
341     private boolean tryToProgress(final Collection<ModifierImpl> currentPhaseModifiers) {
342         boolean hasProgressed = false;
343
344         // We are about to iterate over the modifiers and invoke callbacks. Those callbacks can end up circling back
345         // and modifying the same collection. This asserts that modifiers should not be modified.
346         delayedModifiers = List.of();
347
348         // Try making forward progress ...
349         final Iterator<ModifierImpl> modifier = currentPhaseModifiers.iterator();
350         while (modifier.hasNext()) {
351             if (modifier.next().tryApply()) {
352                 modifier.remove();
353                 hasProgressed = true;
354             }
355         }
356
357         // We have finished iterating, if we have any delayed modifiers, put them back. This may seem as if we want
358         // to retry the loop, but we do not have to, as we will be circling back anyway.
359         //
360         // The thing is, we are inherently single-threaded and therefore if we observe non-empty delayedModifiers, the
361         // only way that could happen is through a callback, which in turn means we have made progress.
362         if (!delayedModifiers.isEmpty()) {
363             verify(hasProgressed, "Delayed modifiers encountered without making progress in %s", this);
364             for (Entry<ModelProcessingPhase, ModifierImpl> entry : delayedModifiers) {
365                 modifiers.put(entry.getKey(), entry.getValue());
366             }
367         }
368         delayedModifiers = null;
369
370         return hasProgressed;
371     }
372
373     @NonNull ModelActionBuilder newInferenceAction(final @NonNull ModelProcessingPhase phase) {
374         final ModifierImpl action = new ModifierImpl();
375
376         if (delayedModifiers != null) {
377             if (delayedModifiers.isEmpty()) {
378                 delayedModifiers = new ArrayList<>(2);
379             }
380             delayedModifiers.add(Map.entry(phase,action));
381         } else {
382             modifiers.put(phase, action);
383         }
384
385         return action;
386     }
387
388     @Override
389     public String toString() {
390         return "SourceSpecificContext [source=" + source + ", current=" + inProgressPhase + ", finished="
391                 + finishedPhase + "]";
392     }
393
394     Optional<SourceException> failModifiers(final ModelProcessingPhase identifier) {
395         final List<SourceException> exceptions = new ArrayList<>();
396         for (final ModifierImpl mod : modifiers.get(identifier)) {
397             try {
398                 mod.failModifier();
399             } catch (final SourceException e) {
400                 exceptions.add(e);
401             }
402         }
403
404         return switch (exceptions.size()) {
405             case 0 -> Optional.empty();
406             case 1 -> Optional.of(exceptions.get(0));
407             default -> {
408                 final String message = String.format("Yang model processing phase %s failed", identifier);
409                 final InferenceException ex = new InferenceException(message, root, exceptions.get(0));
410                 exceptions.listIterator(1).forEachRemaining(ex::addSuppressed);
411                 yield Optional.of(ex);
412             }
413         };
414     }
415
416     void loadStatements() {
417         LOG.trace("Source {} loading statements for phase {}", source, inProgressPhase);
418
419         switch (inProgressPhase) {
420             case SOURCE_PRE_LINKAGE:
421                 source.writePreLinkage(new StatementContextWriter(this, inProgressPhase), stmtDef());
422                 break;
423             case SOURCE_LINKAGE:
424                 source.writeLinkage(new StatementContextWriter(this, inProgressPhase), stmtDef(), preLinkagePrefixes(),
425                     getRootVersion());
426                 break;
427             case STATEMENT_DEFINITION:
428                 source.writeLinkageAndStatementDefinitions(new StatementContextWriter(this, inProgressPhase), stmtDef(),
429                     prefixes(), getRootVersion());
430                 break;
431             case FULL_DECLARATION:
432                 source.writeFull(new StatementContextWriter(this, inProgressPhase), stmtDef(), prefixes(),
433                     getRootVersion());
434                 break;
435             default:
436                 break;
437         }
438     }
439
440     private PrefixResolver preLinkagePrefixes() {
441         final HashMapPrefixResolver preLinkagePrefixes = new HashMapPrefixResolver();
442         final var prefixToNamespaceMap = getAllFromLocalStorage(ParserNamespaces.IMP_PREFIX_TO_NAMESPACE);
443         if (prefixToNamespaceMap == null) {
444             //:FIXME if it is a submodule without any import, the map is null. Handle also submodules and includes...
445             return null;
446         }
447
448         prefixToNamespaceMap.forEach((key, value) -> preLinkagePrefixes.put(key, QNameModule.create(value)));
449         return preLinkagePrefixes;
450     }
451
452     private PrefixResolver prefixes() {
453         final var allImports = root.namespace(ParserNamespaces.IMPORT_PREFIX_TO_MODULECTX);
454         if (allImports != null) {
455             allImports.forEach((key, value) ->
456                 prefixToModuleMap.put(key, root.namespaceItem(ParserNamespaces.MODULECTX_TO_QNAME, value)));
457         }
458
459         final var allBelongsTo = root.namespace(ParserNamespaces.BELONGSTO_PREFIX_TO_MODULECTX);
460         if (allBelongsTo != null) {
461             allBelongsTo.forEach((key, value) ->
462                 prefixToModuleMap.put(key, root.namespaceItem(ParserNamespaces.MODULECTX_TO_QNAME, value)));
463         }
464
465         return prefixToModuleMap;
466     }
467
468     private QNameToStatementDefinition stmtDef() {
469         // regular YANG statements and extension supports added
470         final StatementSupportBundle supportsForPhase = globalContext.getSupportsForPhase(inProgressPhase);
471         qnameToStmtDefMap.putAll(supportsForPhase.getCommonDefinitions());
472         qnameToStmtDefMap.putAll(supportsForPhase.getDefinitionsSpecificForVersion(getRootVersion()));
473
474         // No further actions needed
475         if (inProgressPhase != ModelProcessingPhase.FULL_DECLARATION) {
476             return qnameToStmtDefMap;
477         }
478
479         // We need to any and all extension statements which have been declared in the context
480         final Map<QName, StatementSupport<?, ?, ?>> extensions = globalContext.getNamespace(
481             StatementDefinitions.NAMESPACE);
482         if (extensions != null) {
483             extensions.forEach((qname, support) -> {
484                 final StatementSupport<?, ?, ?> existing = qnameToStmtDefMap.putIfAbsent(qname, support);
485                 if (existing != null) {
486                     LOG.debug("Source {} already defines statement {} as {}", source, qname, existing);
487                 } else {
488                     LOG.debug("Source {} defined statement {} as {}", source, qname, support);
489                 }
490             });
491         }
492
493         return qnameToStmtDefMap;
494     }
495
496     Collection<SourceIdentifier> getRequiredSources() {
497         return root.getRequiredSources();
498     }
499
500     SourceIdentifier getRootIdentifier() {
501         return root.getRootIdentifier();
502     }
503 }