Rename NamespaceStorageSupport
[yangtools.git] / parser / yang-parser-reactor / src / main / java / org / opendaylight / yangtools / yang / parser / stmt / reactor / BuildGlobalContext.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.HashBasedTable;
16 import com.google.common.collect.ImmutableMap;
17 import com.google.common.collect.ImmutableSet;
18 import com.google.common.collect.ImmutableSetMultimap;
19 import com.google.common.collect.SetMultimap;
20 import com.google.common.collect.Table;
21 import com.google.common.collect.TreeBasedTable;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Objects;
30 import java.util.Optional;
31 import java.util.Set;
32 import java.util.SortedMap;
33 import org.eclipse.jdt.annotation.NonNull;
34 import org.opendaylight.yangtools.yang.common.Empty;
35 import org.opendaylight.yangtools.yang.common.QName;
36 import org.opendaylight.yangtools.yang.common.QNameModule;
37 import org.opendaylight.yangtools.yang.common.Revision;
38 import org.opendaylight.yangtools.yang.common.UnresolvedQName.Unqualified;
39 import org.opendaylight.yangtools.yang.common.YangVersion;
40 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
41 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
42 import org.opendaylight.yangtools.yang.model.repo.api.FeatureSet;
43 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
44 import org.opendaylight.yangtools.yang.parser.spi.ParserNamespaces;
45 import org.opendaylight.yangtools.yang.parser.spi.meta.DerivedNamespaceBehaviour;
46 import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase;
47 import org.opendaylight.yangtools.yang.parser.spi.meta.MutableStatement;
48 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceBehaviour;
49 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceNotAvailableException;
50 import org.opendaylight.yangtools.yang.parser.spi.meta.NamespaceStorage;
51 import org.opendaylight.yangtools.yang.parser.spi.meta.ParserNamespace;
52 import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
53 import org.opendaylight.yangtools.yang.parser.spi.meta.SomeModifiersUnresolvedException;
54 import org.opendaylight.yangtools.yang.parser.spi.meta.StatementSupportBundle;
55 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
56 import org.opendaylight.yangtools.yang.parser.spi.source.StatementStreamSource;
57 import org.opendaylight.yangtools.yang.parser.spi.validation.ValidationBundles;
58 import org.opendaylight.yangtools.yang.parser.spi.validation.ValidationBundles.ValidationBundleType;
59 import org.opendaylight.yangtools.yang.parser.stmt.reactor.SourceSpecificContext.PhaseCompletionProgress;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62
63 final class BuildGlobalContext extends AbstractNamespaceStorage {
64     private static final Logger LOG = LoggerFactory.getLogger(BuildGlobalContext.class);
65
66     private static final ModelProcessingPhase[] PHASE_EXECUTION_ORDER = {
67         ModelProcessingPhase.SOURCE_PRE_LINKAGE,
68         ModelProcessingPhase.SOURCE_LINKAGE,
69         ModelProcessingPhase.STATEMENT_DEFINITION,
70         ModelProcessingPhase.FULL_DECLARATION,
71         ModelProcessingPhase.EFFECTIVE_MODEL
72     };
73
74     private final Table<YangVersion, QName, StatementDefinitionContext<?, ?, ?>> definitions = HashBasedTable.create();
75     private final Map<QName, StatementDefinitionContext<?, ?, ?>> modelDefinedStmtDefs = new HashMap<>();
76     private final Map<ParserNamespace<?, ?>, NamespaceBehaviourWithListeners<?, ?>> supportedNamespaces =
77         new HashMap<>();
78     private final List<MutableStatement> mutableStatementsToSeal = new ArrayList<>();
79     private final ImmutableMap<ModelProcessingPhase, StatementSupportBundle> supports;
80     private final Set<SourceSpecificContext> sources = new HashSet<>();
81     private final ImmutableSet<YangVersion> supportedVersions;
82
83     private Set<SourceSpecificContext> libSources = new HashSet<>();
84     private ModelProcessingPhase currentPhase = ModelProcessingPhase.INIT;
85     private ModelProcessingPhase finishedPhase = ModelProcessingPhase.INIT;
86
87     BuildGlobalContext(final ImmutableMap<ModelProcessingPhase, StatementSupportBundle> supports,
88             final ImmutableMap<ValidationBundleType, Collection<?>> supportedValidation) {
89         this.supports = requireNonNull(supports, "BuildGlobalContext#supports cannot be null");
90
91         final var behavior = getNamespaceBehaviour(ValidationBundles.NAMESPACE);
92         for (var validationBundle : supportedValidation.entrySet()) {
93             behavior.addTo(this, validationBundle.getKey(), validationBundle.getValue());
94         }
95
96         supportedVersions = ImmutableSet.copyOf(
97             verifyNotNull(supports.get(ModelProcessingPhase.INIT)).getSupportedVersions());
98     }
99
100     StatementSupportBundle getSupportsForPhase(final ModelProcessingPhase phase) {
101         return supports.get(phase);
102     }
103
104     void addSource(final @NonNull StatementStreamSource source) {
105         sources.add(new SourceSpecificContext(this, source));
106     }
107
108     void addLibSource(final @NonNull StatementStreamSource libSource) {
109         checkState(currentPhase == ModelProcessingPhase.INIT,
110                 "Add library source is allowed in ModelProcessingPhase.INIT only");
111         libSources.add(new SourceSpecificContext(this, libSource));
112     }
113
114     void setSupportedFeatures(final Set<QName> supportedFeatures) {
115         if (supportedFeatures instanceof FeatureSet) {
116             addToNamespace(ParserNamespaces.SUPPORTED_FEATURES, Empty.value(), supportedFeatures);
117         } else {
118             addToNamespace(ParserNamespaces.SUPPORTED_FEATURES, Empty.value(), ImmutableSet.copyOf(supportedFeatures));
119         }
120     }
121
122     void setModulesDeviatedByModules(final SetMultimap<QNameModule, QNameModule> modulesDeviatedByModules) {
123         addToNamespace(ParserNamespaces.MODULES_DEVIATED_BY, Empty.value(),
124             ImmutableSetMultimap.copyOf(modulesDeviatedByModules));
125     }
126
127     @Override
128     public StorageType getStorageType() {
129         return StorageType.GLOBAL;
130     }
131
132     @Override
133     public NamespaceStorage getParentStorage() {
134         return null;
135     }
136
137     @Override
138     <K, V> NamespaceBehaviourWithListeners<K, V> getNamespaceBehaviour(final ParserNamespace<K, V> type) {
139         NamespaceBehaviourWithListeners<?, ?> potential = supportedNamespaces.get(type);
140         if (potential == null) {
141             final var potentialRaw = verifyNotNull(supports.get(currentPhase)).namespaceBehaviourOf(type);
142             if (potentialRaw != null) {
143                 potential = createNamespaceContext(potentialRaw);
144                 supportedNamespaces.put(type, potential);
145             } else {
146                 throw new NamespaceNotAvailableException("Namespace " + type + " is not available in phase "
147                         + currentPhase);
148             }
149         }
150
151         verify(type.equals(potential.namespace()));
152         /*
153          * Safe cast, previous checkState checks equivalence of key from which
154          * type argument are derived
155          */
156         return (NamespaceBehaviourWithListeners<K, V>) potential;
157     }
158
159     @SuppressWarnings({ "unchecked", "rawtypes" })
160     private <K, V> NamespaceBehaviourWithListeners<K, V> createNamespaceContext(
161             final NamespaceBehaviour<K, V> potentialRaw) {
162         if (potentialRaw instanceof DerivedNamespaceBehaviour derived) {
163             final VirtualNamespaceContext derivedContext = new VirtualNamespaceContext(derived);
164             getNamespaceBehaviour(derived.getDerivedFrom()).addDerivedNamespace(derivedContext);
165             return derivedContext;
166         }
167         return new SimpleNamespaceContext<>(potentialRaw);
168     }
169
170     StatementDefinitionContext<?, ?, ?> getStatementDefinition(final YangVersion version, final QName name) {
171         StatementDefinitionContext<?, ?, ?> potential = definitions.get(version, name);
172         if (potential == null) {
173             final var potentialRaw = verifyNotNull(supports.get(currentPhase)).getStatementDefinition(version, name);
174             if (potentialRaw != null) {
175                 potential = new StatementDefinitionContext<>(potentialRaw);
176                 definitions.put(version, name, potential);
177             }
178         }
179         return potential;
180     }
181
182     StatementDefinitionContext<?, ?, ?> getModelDefinedStatementDefinition(final QName name) {
183         return modelDefinedStmtDefs.get(name);
184     }
185
186     void putModelDefinedStatementDefinition(final QName name, final StatementDefinitionContext<?, ?, ?> def) {
187         modelDefinedStmtDefs.put(name, def);
188     }
189
190     private void executePhases() throws ReactorException {
191         for (final ModelProcessingPhase phase : PHASE_EXECUTION_ORDER) {
192             startPhase(phase);
193             loadPhaseStatements();
194             completePhaseActions();
195             endPhase(phase);
196         }
197     }
198
199     ReactorDeclaredModel build() throws ReactorException {
200         executePhases();
201         return transform();
202     }
203
204     EffectiveSchemaContext buildEffective() throws ReactorException {
205         executePhases();
206         return transformEffective();
207     }
208
209     private ReactorDeclaredModel transform() {
210         checkState(finishedPhase == ModelProcessingPhase.EFFECTIVE_MODEL);
211         final var rootStatements = new ArrayList<DeclaredStatement<?>>(sources.size());
212         for (var source : sources) {
213             rootStatements.add(source.declaredRoot());
214         }
215         return new ReactorDeclaredModel(rootStatements);
216     }
217
218     private SomeModifiersUnresolvedException propagateException(final SourceSpecificContext source,
219             final RuntimeException cause) throws SomeModifiersUnresolvedException {
220         final SourceIdentifier sourceId = source.identifySource();
221         if (!(cause instanceof SourceException)) {
222             /*
223              * This should not be happening as all our processing should provide SourceExceptions.
224              * We will wrap the exception to provide enough information to identify the problematic model,
225              * but also emit a warning so the offending codepath will get fixed.
226              */
227             LOG.warn("Unexpected error processing source {}. Please file an issue with this model attached.",
228                 sourceId, cause);
229         }
230
231         throw new SomeModifiersUnresolvedException(currentPhase, sourceId, cause);
232     }
233
234     @SuppressWarnings("checkstyle:illegalCatch")
235     private EffectiveSchemaContext transformEffective() throws ReactorException {
236         checkState(finishedPhase == ModelProcessingPhase.EFFECTIVE_MODEL);
237         final var rootStatements = new ArrayList<DeclaredStatement<?>>(sources.size());
238         final var rootEffectiveStatements = new ArrayList<EffectiveStatement<?, ?>>(sources.size());
239
240         for (var source : sources) {
241             try {
242                 rootStatements.add(source.declaredRoot());
243                 rootEffectiveStatements.add(source.effectiveRoot());
244             } catch (final RuntimeException ex) {
245                 throw propagateException(source, ex);
246             }
247         }
248
249         sealMutableStatements();
250         return EffectiveSchemaContext.create(rootStatements, rootEffectiveStatements);
251     }
252
253     private void startPhase(final ModelProcessingPhase phase) {
254         checkState(Objects.equals(finishedPhase, phase.getPreviousPhase()));
255         startPhaseFor(phase, sources);
256         startPhaseFor(phase, libSources);
257
258         currentPhase = phase;
259         LOG.debug("Global phase {} started", phase);
260     }
261
262     private static void startPhaseFor(final ModelProcessingPhase phase, final Set<SourceSpecificContext> sources) {
263         for (final SourceSpecificContext source : sources) {
264             source.startPhase(phase);
265         }
266     }
267
268     private void loadPhaseStatements() throws ReactorException {
269         checkState(currentPhase != null);
270         loadPhaseStatementsFor(sources);
271         loadPhaseStatementsFor(libSources);
272     }
273
274     @SuppressWarnings("checkstyle:illegalCatch")
275     private void loadPhaseStatementsFor(final Set<SourceSpecificContext> srcs) throws ReactorException {
276         for (final SourceSpecificContext source : srcs) {
277             try {
278                 source.loadStatements();
279             } catch (final RuntimeException ex) {
280                 throw propagateException(source, ex);
281             }
282         }
283     }
284
285     private SomeModifiersUnresolvedException addSourceExceptions(final List<SourceSpecificContext> sourcesToProgress) {
286         boolean addedCause = false;
287         SomeModifiersUnresolvedException buildFailure = null;
288         for (final SourceSpecificContext failedSource : sourcesToProgress) {
289             final Optional<SourceException> optSourceEx = failedSource.failModifiers(currentPhase);
290             if (optSourceEx.isEmpty()) {
291                 continue;
292             }
293
294             final SourceException sourceEx = optSourceEx.get();
295             // Workaround for broken logging implementations which ignore
296             // suppressed exceptions
297             final Throwable cause = sourceEx.getCause() != null ? sourceEx.getCause() : sourceEx;
298             if (LOG.isDebugEnabled()) {
299                 LOG.error("Failed to parse YANG from source {}", failedSource, sourceEx);
300             } else {
301                 LOG.error("Failed to parse YANG from source {}: {}", failedSource, cause.getMessage());
302             }
303
304             final Throwable[] suppressed = sourceEx.getSuppressed();
305             if (suppressed.length > 0) {
306                 LOG.error("{} additional errors reported:", suppressed.length);
307
308                 int count = 1;
309                 for (final Throwable t : suppressed) {
310                     LOG.error("Error {}: {}", count, t.getMessage());
311                     count++;
312                 }
313             }
314
315             if (!addedCause) {
316                 addedCause = true;
317                 final SourceIdentifier sourceId = failedSource.identifySource();
318                 buildFailure = new SomeModifiersUnresolvedException(currentPhase, sourceId, sourceEx);
319             } else {
320                 buildFailure.addSuppressed(sourceEx);
321             }
322         }
323         return buildFailure;
324     }
325
326     @SuppressWarnings("checkstyle:illegalCatch")
327     private void completePhaseActions() throws ReactorException {
328         checkState(currentPhase != null);
329         final List<SourceSpecificContext> sourcesToProgress = new ArrayList<>(sources);
330         if (!libSources.isEmpty()) {
331             checkState(currentPhase == ModelProcessingPhase.SOURCE_PRE_LINKAGE,
332                     "Yang library sources should be empty after ModelProcessingPhase.SOURCE_PRE_LINKAGE, "
333                             + "but current phase was %s", currentPhase);
334             sourcesToProgress.addAll(libSources);
335         }
336
337         boolean progressing = true;
338         while (progressing) {
339             // We reset progressing to false.
340             progressing = false;
341             final Iterator<SourceSpecificContext> currentSource = sourcesToProgress.iterator();
342             while (currentSource.hasNext()) {
343                 final SourceSpecificContext nextSourceCtx = currentSource.next();
344                 try {
345                     final PhaseCompletionProgress sourceProgress =
346                         nextSourceCtx.tryToCompletePhase(currentPhase.executionOrder());
347                     switch (sourceProgress) {
348                         case FINISHED:
349                             currentSource.remove();
350                             // we were able to make progress in computation
351                             progressing = true;
352                             break;
353                         case PROGRESS:
354                             progressing = true;
355                             break;
356                         case NO_PROGRESS:
357                             // Noop
358                             break;
359                         default:
360                             throw new IllegalStateException("Unsupported phase progress " + sourceProgress);
361                     }
362                 } catch (final RuntimeException ex) {
363                     throw propagateException(nextSourceCtx, ex);
364                 }
365             }
366         }
367
368         if (!libSources.isEmpty()) {
369             final Set<SourceSpecificContext> requiredLibs = getRequiredSourcesFromLib();
370             sources.addAll(requiredLibs);
371             libSources = ImmutableSet.of();
372             /*
373              * We want to report errors of relevant sources only, so any others can
374              * be removed.
375              */
376             sourcesToProgress.retainAll(sources);
377         }
378
379         if (!sourcesToProgress.isEmpty()) {
380             final SomeModifiersUnresolvedException buildFailure = addSourceExceptions(sourcesToProgress);
381             if (buildFailure != null) {
382                 throw buildFailure;
383             }
384         }
385     }
386
387     private Set<SourceSpecificContext> getRequiredSourcesFromLib() {
388         checkState(currentPhase == ModelProcessingPhase.SOURCE_PRE_LINKAGE,
389                 "Required library sources can be collected only in ModelProcessingPhase.SOURCE_PRE_LINKAGE phase,"
390                         + " but current phase was %s", currentPhase);
391         final TreeBasedTable<Unqualified, Optional<Revision>, SourceSpecificContext> libSourcesTable =
392             TreeBasedTable.create(Unqualified::compareTo, Revision::compare);
393         for (final SourceSpecificContext libSource : libSources) {
394             final SourceIdentifier libSourceIdentifier = requireNonNull(libSource.getRootIdentifier());
395             libSourcesTable.put(libSourceIdentifier.name(),
396                 Optional.ofNullable(libSourceIdentifier.revision()), libSource);
397         }
398
399         final Set<SourceSpecificContext> requiredLibs = new HashSet<>();
400         for (final SourceSpecificContext source : sources) {
401             collectRequiredSourcesFromLib(libSourcesTable, requiredLibs, source);
402             removeConflictingLibSources(source, requiredLibs);
403         }
404         return requiredLibs;
405     }
406
407     private void collectRequiredSourcesFromLib(
408             final TreeBasedTable<Unqualified, Optional<Revision>, SourceSpecificContext> libSourcesTable,
409             final Set<SourceSpecificContext> requiredLibs, final SourceSpecificContext source) {
410         for (final SourceIdentifier requiredSource : source.getRequiredSources()) {
411             final SourceSpecificContext libSource = getRequiredLibSource(requiredSource, libSourcesTable);
412             if (libSource != null && requiredLibs.add(libSource)) {
413                 collectRequiredSourcesFromLib(libSourcesTable, requiredLibs, libSource);
414             }
415         }
416     }
417
418     private static SourceSpecificContext getRequiredLibSource(final SourceIdentifier requiredSource,
419             final TreeBasedTable<Unqualified, Optional<Revision>, SourceSpecificContext> libSourcesTable) {
420         final var revision = requiredSource.revision();
421         return revision != null ? libSourcesTable.get(requiredSource.name(), Optional.of(revision))
422             : getLatestRevision(libSourcesTable.row(requiredSource.name()));
423     }
424
425     private static SourceSpecificContext getLatestRevision(
426             final SortedMap<Optional<Revision>, SourceSpecificContext> sourceMap) {
427         return sourceMap != null && !sourceMap.isEmpty() ? sourceMap.get(sourceMap.lastKey()) : null;
428     }
429
430     // removes required library sources which would cause namespace/name conflict with one of the main sources
431     // later in the parsing process. this can happen if we add a parent module or a submodule as a main source
432     // and the same parent module or submodule is added as one of the library sources.
433     // such situation may occur when using the yang-system-test artifact - if a parent module/submodule is specified
434     // as its argument and the same dir is specified as one of the library dirs through -p option).
435     private static void removeConflictingLibSources(final SourceSpecificContext source,
436             final Set<SourceSpecificContext> requiredLibs) {
437         final Iterator<SourceSpecificContext> requiredLibsIter = requiredLibs.iterator();
438         while (requiredLibsIter.hasNext()) {
439             final SourceSpecificContext currentReqSource = requiredLibsIter.next();
440             if (source.getRootIdentifier().equals(currentReqSource.getRootIdentifier())) {
441                 requiredLibsIter.remove();
442             }
443         }
444     }
445
446     private void endPhase(final ModelProcessingPhase phase) {
447         checkState(currentPhase == phase);
448         finishedPhase = currentPhase;
449         LOG.debug("Global phase {} finished", phase);
450     }
451
452     Set<SourceSpecificContext> getSources() {
453         return sources;
454     }
455
456     public Set<YangVersion> getSupportedVersions() {
457         return supportedVersions;
458     }
459
460     void addMutableStmtToSeal(final MutableStatement mutableStatement) {
461         mutableStatementsToSeal.add(mutableStatement);
462     }
463
464     void sealMutableStatements() {
465         for (final MutableStatement mutableStatement : mutableStatementsToSeal) {
466             mutableStatement.seal();
467         }
468         mutableStatementsToSeal.clear();
469     }
470 }