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