16ba9878bbe41df9ac7690e25243c235196beb58
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / repo / DependencyResolver.java
1 /*
2  * Copyright (c) 2014 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.repo;
9
10 import com.google.common.base.MoreObjects;
11 import com.google.common.collect.ArrayListMultimap;
12 import com.google.common.collect.ImmutableList;
13 import com.google.common.collect.ImmutableMultimap;
14 import com.google.common.collect.Maps;
15 import com.google.common.collect.Multimap;
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.Date;
19 import java.util.Iterator;
20 import java.util.Map;
21 import java.util.Map.Entry;
22 import java.util.Set;
23 import org.opendaylight.yangtools.concepts.SemVer;
24 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
25 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
26 import org.opendaylight.yangtools.yang.parser.impl.util.YangModelDependencyInfo;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 /**
31  * Inter-module dependency resolved. Given a set of schema source identifiers and their
32  * corresponding dependency information, the {@link #create(Map)} method creates a
33  * a view of how consistent the dependencies are. In particular, this detects whether
34  * any imports are unsatisfied.
35  */
36 // FIXME: improve this class to track and expose how wildcard imports were resolved.
37 //        That information will allow us to track "damage" to dependency resolution
38 //        as new models are added to a schema context.
39 abstract class DependencyResolver {
40     private static final Logger LOG = LoggerFactory.getLogger(DependencyResolver.class);
41     private final Collection<SourceIdentifier> resolvedSources;
42     private final Collection<SourceIdentifier> unresolvedSources;
43     private final Multimap<SourceIdentifier, ModuleImport> unsatisfiedImports;
44
45     protected DependencyResolver(final Map<SourceIdentifier, YangModelDependencyInfo> depInfo) {
46         final Collection<SourceIdentifier> resolved = new ArrayList<>(depInfo.size());
47         final Collection<SourceIdentifier> pending = new ArrayList<>(depInfo.keySet());
48         final Map<SourceIdentifier, BelongsToDependency> submodules = Maps.newHashMap();
49
50         boolean progress;
51         do {
52             progress = false;
53
54             final Iterator<SourceIdentifier> it = pending.iterator();
55             while (it.hasNext()) {
56                 final SourceIdentifier id = it.next();
57                 final YangModelDependencyInfo dep = depInfo.get(id);
58
59                 boolean okay = true;
60
61                 final Set<ModuleImport> dependencies = dep.getDependencies();
62
63                 // in case of submodule, remember belongs to
64                 if (dep instanceof YangModelDependencyInfo.SubmoduleDependencyInfo) {
65                     final String parent = ((YangModelDependencyInfo.SubmoduleDependencyInfo) dep).getParentModule();
66                     submodules.put(id, new BelongsToDependency(parent));
67                 }
68
69                 for (final ModuleImport mi : dependencies) {
70                     if (!isKnown(resolved, mi)) {
71                         LOG.debug("Source {} is missing import {}", id, mi);
72                         okay = false;
73                         break;
74                     }
75                 }
76
77                 if (okay) {
78                     LOG.debug("Resolved source {}", id);
79                     resolved.add(id);
80                     it.remove();
81                     progress = true;
82                 }
83             }
84         } while (progress);
85
86         /// Additional check only for belongs-to statement
87         for (final Entry<SourceIdentifier, BelongsToDependency> submodule : submodules.entrySet()) {
88             final BelongsToDependency belongs = submodule.getValue();
89             final SourceIdentifier sourceIdentifier = submodule.getKey();
90             if (!isKnown(resolved, belongs)) {
91                 LOG.debug("Source {} is missing parent {}", sourceIdentifier, belongs);
92                 pending.add(sourceIdentifier);
93                 resolved.remove(sourceIdentifier);
94             }
95         }
96
97         final Multimap<SourceIdentifier, ModuleImport> imports = ArrayListMultimap.create();
98         for (final SourceIdentifier id : pending) {
99             final YangModelDependencyInfo dep = depInfo.get(id);
100             for (final ModuleImport mi : dep.getDependencies()) {
101                 if (!isKnown(pending, mi) && !isKnown(resolved, mi)) {
102                     imports.put(id, mi);
103                 }
104             }
105         }
106
107         this.resolvedSources = ImmutableList.copyOf(resolved);
108         this.unresolvedSources = ImmutableList.copyOf(pending);
109         this.unsatisfiedImports = ImmutableMultimap.copyOf(imports);
110     }
111
112     protected abstract boolean isKnown(Collection<SourceIdentifier> haystack, ModuleImport mi);
113
114     /**
115      * Collection of sources which have been resolved.
116      */
117     Collection<SourceIdentifier> getResolvedSources() {
118         return resolvedSources;
119     }
120
121     /**
122      * Collection of sources which have not been resolved due to missing dependencies.
123      */
124     Collection<SourceIdentifier> getUnresolvedSources() {
125         return unresolvedSources;
126     }
127
128     /**
129      * Detailed information about which imports were missing. The key in the map
130      * is the source identifier of module which was issuing an import, the values
131      * are imports which were unsatisfied.
132      *
133      * <p>
134      * Note that this map contains only imports which are missing from the reactor,
135      * not transitive failures.
136      *
137      * <p>
138      * Examples:
139      * <ul><li>
140      * If A imports B, B imports C, and both A and B are in the reactor, only B->C
141      * will be reported.
142      * </li><li>
143      * If A imports B and C, B imports C, and both A and B are in the reactor,
144      * A->C and B->C will be reported.
145      * </li></ul>
146      */
147     Multimap<SourceIdentifier, ModuleImport> getUnsatisfiedImports() {
148         return unsatisfiedImports;
149     }
150
151     private static class BelongsToDependency implements ModuleImport {
152         private final String parent;
153
154         BelongsToDependency(final String parent) {
155             this.parent = parent;
156         }
157
158         @Override
159         public String getModuleName() {
160             return parent;
161         }
162
163         @Override
164         public Date getRevision() {
165             return null;
166         }
167
168         @Override
169         public String getPrefix() {
170             return null;
171         }
172
173         @Override
174         public SemVer getSemanticVersion() {
175             return null;
176         }
177
178         @Override
179         public String toString() {
180             return MoreObjects.toStringHelper(this)
181                     .add("parent", parent)
182                     .toString();
183         }
184     }
185 }