Bug 1131 - yang-parser-impl cleanup
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / impl / util / YangSourceContextResolver.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
3  * This program and the accompanying materials are made available under the
4  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
5  * and is available at http://www.eclipse.org/legal/eplv10.html
6  */
7 package org.opendaylight.yangtools.yang.parser.impl.util;
8
9 import static com.google.common.base.Preconditions.checkNotNull;
10
11 import com.google.common.base.Optional;
12 import com.google.common.collect.ImmutableMultimap;
13 import com.google.common.collect.ImmutableSet;
14 import java.io.InputStream;
15 import java.util.HashMap;
16 import javax.annotation.concurrent.NotThreadSafe;
17 import org.opendaylight.yangtools.yang.common.QName;
18 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
19 import org.opendaylight.yangtools.yang.model.util.repo.AdvancedSchemaSourceProvider;
20 import org.opendaylight.yangtools.yang.model.util.repo.SchemaSourceProvider;
21 import org.opendaylight.yangtools.yang.model.util.repo.SourceIdentifier;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
24
25 /**
26  *
27  * Resolution task for YANG Source Context
28  *
29  * {@link YangSourceContextResolver} and its subclasses are responsible for
30  * resolving {@link YangSourceContext} based on provided
31  * {@link SchemaSourceProvider} and set of modules to process.
32  *
33  *
34  * <h3>Implementation notes</h3>
35  *
36  * In order to customize resolution of {@link YangSourceContext} implementators
37  * of this class are required to implement following methods:
38  * <ul>
39  * <li>{@link #getDependencyInfo(SourceIdentifier)} - Retrieval of dependency
40  * information</li>
41  * <li>{@link #resolveContext()} - Main resolution algorithm
42  * <li>
43  * </ul>
44  *
45  * This abstract class provides utility methods for implementators which may be
46  * used in {@link #resolveContext()} to create {@link YangSourceContext}:
47  * <ul>
48  * <li>{@link #resolveSource(SourceIdentifier)} and
49  * {@link #resolveSource(String, Optional)} - Tries to resolve state for
50  * supplied model identifier and updates internal state. If state was not
51  * already resolved for identifier it invokes
52  * {@link #getDependencyInfo(SourceIdentifier)} for particular identifier. This
53  * method is recursively invoked for all dependencies.</li>
54  * <li>{@link #createSourceContext()} - Creates {@link YangSourceContext} based
55  * on previous invocations of {@link #resolveSource(SourceIdentifier)} methods.</li>
56  * </ul>
57  *
58  */
59 @NotThreadSafe
60 public abstract class YangSourceContextResolver {
61
62     /**
63      * 
64      * State of source code resolution
65      * 
66      */
67     public enum ResolutionState {
68         /**
69          * 
70          * Source was missing during source resolution
71          * 
72          */
73         MISSING_SOURCE, 
74         /**
75          * 
76          * One or multiple of dependencies of source are missing 
77          * 
78          */
79         MISSING_DEPENDENCY, 
80         /**
81          * Other error ocurred during resolution
82          * 
83          */
84         OTHER_ERROR, 
85         /**
86          * Source, its dependencies and its transient dependencies
87          * are resolved.
88          * 
89          */
90         EVERYTHING_OK,
91     }
92
93     private static final Logger LOG = LoggerFactory.getLogger(YangSourceContextResolver.class);
94     private final HashMap<SourceIdentifier, YangSourceContextResolver.ResolutionState> alreadyProcessed = new HashMap<>();
95     private final ImmutableSet.Builder<SourceIdentifier> missingSources = ImmutableSet.builder();
96     private final ImmutableMultimap.Builder<SourceIdentifier, ModuleImport> missingDependencies = ImmutableMultimap
97             .builder();
98     private final ImmutableSet.Builder<SourceIdentifier> validSources = ImmutableSet.builder();
99     private final AdvancedSchemaSourceProvider<InputStream> sourceProvider;
100
101     public YangSourceContextResolver(final AdvancedSchemaSourceProvider<InputStream> sourceProvider) {
102         this.sourceProvider = checkNotNull(sourceProvider, "Missing sourceProvider");
103     }
104
105     /**
106      * Resolves {@link YangSourceContext}
107      *
108      * Implementators of this method should invoke
109      * {@link #resolveSource(SourceIdentifier)} for sources which should be
110      * present in {@link YangSourceContext} and {@link #createSourceContext()}
111      * to create resulting {@link YangSourceContext} which will contain state
112      * derived by callbacks to {@link #getDependencyInfo(SourceIdentifier)}.
113      *
114      * @return Resolved {@link YangSourceContext}.
115      */
116     public abstract YangSourceContext resolveContext();
117
118     /**
119      * Returns dependency information for provided identifier
120      *
121      * Implementations are required to:
122      * <ul>
123      * <li>return {@link Optional#absent()} If source code for source is not
124      * present</li>
125      * <li>return same dependency information for multiple invocations of this
126      * method for same source identifier.</li>
127      * <li>return latest available revision if {@link SourceIdentifier} does not
128      * specify revision. If no revision is available {@link Optional#absent()}
129      * MUST be returned.</li>
130      * </ul>
131      *
132      *
133      * Internal state of this object (and resulting {@link YangSourceContext}
134      * will be updated as following:
135      * <ul>
136      * <li>If {@link Optional#absent()} is returned:
137      * <ul>
138      * <li>source will be marked as {@link ResolutionState#MISSING_SOURCE} and
139      * source identifier will be contained in -
140      * {@link YangSourceContext#getMissingSources()}</li>
141      * <li>All sources which imported or included this source will be present in
142      * {@link YangSourceContext#getMissingDependencies()}</li>
143      * </ul>
144      *
145      *
146      *
147      * @param identifier
148      *            Source identifier
149      * @return Dependency Information for {@link SourceIdentifier},
150      *         {@link Optional#absent()} if no source is present.
151      */
152     abstract Optional<YangModelDependencyInfo> getDependencyInfo(SourceIdentifier identifier);
153
154     /**
155      * Return Source provider against which YANG source context was computed
156      *
157      * @return Source provider against which YANG source context was computed or null, if source provider
158      *   is not associated with computation.
159      */
160     public AdvancedSchemaSourceProvider<InputStream> getSourceProvider() {
161         return sourceProvider;
162     }
163
164     /**
165      *
166      * Resolves resolution state for provided name and formated revision
167      *
168      * This method is shorthand for {@link #resolveSource(SourceIdentifier)}
169      * with argument <code>new SourceIdentifier(name, formattedRevision)</code>
170      *
171      * @see #resolveSource(SourceIdentifier)
172      * @param name
173      *            Name of YANG model
174      * @param formattedRevision
175      *            revision of YANG model
176      * @return Resolution context of YANG Source
177      */
178     public final YangSourceContextResolver.ResolutionState resolveSource(final String name,
179             final Optional<String> formattedRevision) {
180         return resolveSource(new SourceIdentifier(name, formattedRevision));
181     }
182
183     /**
184      * Resolves state of source and updates internal state accordingly.
185      *
186      * <p>
187      * Resolves state of source and updates internal state based on resolution.
188      * This method tries to get module dependency info via user implementation
189      * of {@link #getDependencyInfo(SourceIdentifier)} and then is recursively
190      * called for each announced dependency in
191      * {@link YangModelDependencyInfo#getDependencies()}.
192      *
193      * <p>
194      * Resolution state of resolveSource is internally cached and is used in
195      * subsequent resolution of dependent modules and in creation of
196      * YANGSourceContext via {@link #createSourceContext()}.
197      *
198      * <p>
199      * Possible resolution state for sources are:
200      * <ul>
201      * <li>{@link ResolutionState#EVERYTHING_OK} - If sources for module and its
202      * dependencies are available</li>
203      * <li>{@link ResolutionState#MISSING_DEPENDENCY} - If dependency of source
204      * is missing (call to {@link #getDependencyInfo(SourceIdentifier)} for
205      * imported / included model returned returned {@link Optional#absent()}.</li>
206      * <li>{@link ResolutionState#MISSING_SOURCE} - If source is missing. (call
207      * of {@link #getDependencyInfo(SourceIdentifier)} returned
208      * {@link Optional#absent()}.</li>
209      * <li>{@link ResolutionState#OTHER_ERROR} - If other runtime error
210      * prevented resolution of informations.</li>
211      * </ul>
212      *
213      * Note: Multiple invocations of this method returns cached result, since
214      * {@link #getDependencyInfo(SourceIdentifier)} contract requires
215      * implementors to return same information during life of this object.
216      *
217      *
218      * @param identifier
219      *            Source Identifier
220      * @return Returns resolution state for source.
221      */
222     public final YangSourceContextResolver.ResolutionState resolveSource(final SourceIdentifier identifier) {
223
224         if (alreadyProcessed.containsKey(identifier)) {
225             return alreadyProcessed.get(identifier);
226         }
227         LOG.trace("Resolving source: {}", identifier);
228         YangSourceContextResolver.ResolutionState potentialState = YangSourceContextResolver.ResolutionState.EVERYTHING_OK;
229         try {
230             Optional<YangModelDependencyInfo> potentialInfo = getDependencyInfo(identifier);
231             if (potentialInfo.isPresent()) {
232                 YangModelDependencyInfo info = potentialInfo.get();
233                 checkValidSource(identifier, info);
234                 for (ModuleImport dependency : info.getDependencies()) {
235                     LOG.trace("Source: {} Resolving dependency: {}", identifier, dependency);
236                     YangSourceContextResolver.ResolutionState dependencyState = resolveDependency(dependency);
237                     if (dependencyState != YangSourceContextResolver.ResolutionState.EVERYTHING_OK) {
238                         potentialState = YangSourceContextResolver.ResolutionState.MISSING_DEPENDENCY;
239                         missingDependencies.put(identifier, dependency);
240                     }
241                 }
242             } else {
243                 missingSources.add(identifier);
244                 return YangSourceContextResolver.ResolutionState.MISSING_SOURCE;
245             }
246         } catch (Exception e) {
247             potentialState = YangSourceContextResolver.ResolutionState.OTHER_ERROR;
248         }
249         updateResolutionState(identifier, potentialState);
250         return potentialState;
251     }
252
253     private boolean checkValidSource(final SourceIdentifier identifier, final YangModelDependencyInfo info) {
254         if (!identifier.getName().equals(info.getName())) {
255             LOG.warn("Incorrect model returned. Identifier name was: {}, source contained: {}", identifier.getName(),
256                     info.getName());
257             throw new IllegalStateException("Incorrect source was returned");
258         }
259         return true;
260     }
261
262     private void updateResolutionState(final SourceIdentifier identifier,
263             final YangSourceContextResolver.ResolutionState potentialState) {
264         alreadyProcessed.put(identifier, potentialState);
265         switch (potentialState) {
266         case MISSING_SOURCE:
267             missingSources.add(identifier);
268             break;
269         case EVERYTHING_OK:
270             validSources.add(identifier);
271             break;
272         default:
273             break;
274         }
275     }
276
277     private YangSourceContextResolver.ResolutionState resolveDependency(final ModuleImport dependency) {
278         String name = dependency.getModuleName();
279         Optional<String> formattedRevision = Optional.fromNullable(QName.formattedRevision(dependency.getRevision()));
280         return resolveSource(new SourceIdentifier(name, formattedRevision));
281     }
282
283     protected YangSourceContext createSourceContext() {
284         ImmutableSet<SourceIdentifier> missingSourcesSet = missingSources.build();
285         ImmutableMultimap<SourceIdentifier, ModuleImport> missingDependenciesMap = missingDependencies.build();
286         ImmutableSet<SourceIdentifier> validSourcesSet = validSources.build();
287         return new YangSourceContext(validSourcesSet, missingSourcesSet, missingDependenciesMap, sourceProvider);
288     }
289 }