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