Remove RevisionSourceIdentifier
[yangtools.git] / model / yang-model-spi / src / main / java / org / opendaylight / yangtools / yang / model / spi / ModuleDependencySort.java
1 /*
2  * Copyright (c) 2013 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.model.spi;
9
10 import com.google.common.annotations.Beta;
11 import com.google.common.collect.HashBasedTable;
12 import com.google.common.collect.Lists;
13 import com.google.common.collect.Table;
14 import java.util.Arrays;
15 import java.util.Collection;
16 import java.util.HashMap;
17 import java.util.HashSet;
18 import java.util.LinkedHashSet;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Objects;
22 import java.util.Optional;
23 import org.opendaylight.yangtools.util.TopologicalSort;
24 import org.opendaylight.yangtools.util.TopologicalSort.Node;
25 import org.opendaylight.yangtools.util.TopologicalSort.NodeImpl;
26 import org.opendaylight.yangtools.yang.common.Revision;
27 import org.opendaylight.yangtools.yang.common.XMLNamespace;
28 import org.opendaylight.yangtools.yang.common.YangVersion;
29 import org.opendaylight.yangtools.yang.model.api.Module;
30 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
31 import org.opendaylight.yangtools.yang.model.api.Submodule;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 /**
36  * Creates a module dependency graph from provided {@link Module}s and provides a {@link #sort(Collection)} method.
37  * It is topological sort and returns modules in order in which they should be processed (e.g. if A imports B, sort
38  * returns {B, A}).
39  */
40 @Beta
41 public final class ModuleDependencySort {
42     private static final Logger LOG = LoggerFactory.getLogger(ModuleDependencySort.class);
43
44     private ModuleDependencySort() {
45         // Hidden on purpose
46     }
47
48     /**
49      * Topological sort of module dependency graph.
50      *
51      * @param modules YANG modules
52      * @return Sorted list of Modules. Modules can be further processed in returned order.
53      * @throws IllegalArgumentException when provided modules are not consistent.
54      */
55     public static List<Module> sort(final Module... modules) {
56         return sort(Arrays.asList(modules));
57     }
58
59     /**
60      * Topological sort of module dependency graph.
61      *
62      * @param modules YANG modules
63      * @return Sorted list of Modules. Modules can be further processed in returned order.
64      * @throws IllegalArgumentException when provided modules are not consistent.
65      */
66     public static List<Module> sort(final Collection<? extends Module> modules) {
67         final List<Node> sorted = sortInternal(modules);
68         // Cast to Module from Node and return
69         return Lists.transform(sorted, input -> input == null ? null : ((ModuleNodeImpl) input).getReference());
70     }
71
72     private static List<Node> sortInternal(final Collection<? extends Module> modules) {
73         final Table<String, Optional<Revision>, ModuleNodeImpl> moduleGraph = createModuleGraph(modules);
74         return TopologicalSort.sort(new HashSet<>(moduleGraph.values()));
75     }
76
77     private static Table<String, Optional<Revision>, ModuleNodeImpl> createModuleGraph(
78             final Collection<? extends Module> builders) {
79         final Table<String, Optional<Revision>, ModuleNodeImpl> moduleGraph = HashBasedTable.create();
80
81         processModules(moduleGraph, builders);
82         processDependencies(moduleGraph, builders);
83
84         return moduleGraph;
85     }
86
87     /**
88      * Extract module:revision from modules.
89      */
90     private static void processDependencies(final Table<String, Optional<Revision>, ModuleNodeImpl> moduleGraph,
91             final Collection<? extends Module> mmbs) {
92         final Map<XMLNamespace, Module> allNS = new HashMap<>();
93
94         // Create edges in graph
95         for (final Module module : mmbs) {
96             final Map<String, Optional<Revision>> imported = new HashMap<>();
97             final String fromName = module.getName();
98             final XMLNamespace ns = module.getNamespace();
99             final Optional<Revision> fromRevision = module.getRevision();
100
101             // check for existence of module with same namespace
102             final Module prev = allNS.putIfAbsent(ns, module);
103             if (prev != null) {
104                 final String name = prev.getName();
105                 if (!fromName.equals(name)) {
106                     LOG.warn("Error while sorting module [{}, {}]: module with same namespace ({}) already loaded:"
107                         + " [{}, {}]", fromName, fromRevision, ns, name, prev.getRevision());
108                 }
109             }
110
111             // no need to check if other Type of object, check is performed in process modules
112             for (final ModuleImport imprt : allImports(module)) {
113                 final String toName = imprt.getModuleName().getLocalName();
114                 final Optional<Revision> toRevision = imprt.getRevision();
115
116                 final ModuleNodeImpl from = moduleGraph.get(fromName, fromRevision);
117                 final ModuleNodeImpl to = getModuleByNameAndRevision(moduleGraph, fromName, fromRevision, toName,
118                     toRevision);
119
120                 /*
121                  * If it is an yang 1 module, check imports: If module is imported twice with different
122                  * revisions then throw exception
123                  */
124                 if (module.getYangVersion() == YangVersion.VERSION_1) {
125                     final Optional<Revision> impRevision = imported.get(toName);
126                     if (impRevision != null && impRevision.isPresent() && !impRevision.equals(toRevision)
127                             && toRevision.isPresent()) {
128                         throw new IllegalArgumentException(String.format(
129                             "Module:%s imported twice with different revisions:%s, %s", toName,
130                             formatRevDate(impRevision), formatRevDate(toRevision)));
131                     }
132                 }
133
134                 imported.put(toName, toRevision);
135
136                 from.addEdge(to);
137             }
138         }
139     }
140
141     private static Collection<? extends ModuleImport> allImports(final Module mod) {
142         if (mod.getSubmodules().isEmpty()) {
143             return mod.getImports();
144         }
145
146         final Collection<ModuleImport> concat = new LinkedHashSet<>();
147         concat.addAll(mod.getImports());
148         for (Submodule sub : mod.getSubmodules()) {
149             concat.addAll(sub.getImports());
150         }
151         return concat;
152     }
153
154     /**
155      * Get imported module by its name and revision from moduleGraph.
156      */
157     private static ModuleNodeImpl getModuleByNameAndRevision(
158             final Table<String, Optional<Revision>, ModuleNodeImpl> moduleGraph, final String fromName,
159             final Optional<Revision> fromRevision, final String toName, final Optional<Revision> toRevision) {
160
161         final ModuleNodeImpl exact = moduleGraph.get(toName, toRevision);
162         if (exact != null) {
163             return exact;
164         }
165
166         // If revision is not specified in import, but module exists with different revisions, take first one
167         if (toRevision.isEmpty()) {
168             final Map<Optional<Revision>, ModuleNodeImpl> modulerevs = moduleGraph.row(toName);
169
170             if (!modulerevs.isEmpty()) {
171                 final ModuleNodeImpl first = modulerevs.values().iterator().next();
172                 if (LOG.isTraceEnabled()) {
173                     LOG.trace("Import:{}:{} by module:{}:{} does not specify revision, using:{}:{}"
174                             + " for module dependency sort", toName, formatRevDate(toRevision), fromName,
175                             formatRevDate(fromRevision), first.getName(), formatRevDate(first.getRevision()));
176                 }
177                 return first;
178             }
179         }
180
181         LOG.warn("Not existing module imported:{}:{} by:{}:{}", toName, formatRevDate(toRevision), fromName,
182             formatRevDate(fromRevision));
183         LOG.warn("Available models: {}", moduleGraph);
184         throw new IllegalArgumentException(String.format("Not existing module imported:%s:%s by:%s:%s", toName,
185             formatRevDate(toRevision), fromName, formatRevDate(fromRevision)));
186     }
187
188     /**
189      * Extract dependencies from modules to fill dependency graph.
190      */
191     private static void processModules(final Table<String, Optional<Revision>, ModuleNodeImpl> moduleGraph,
192             final Iterable<? extends Module> modules) {
193
194         // Process nodes
195         for (final Module momb : modules) {
196
197             final String name = momb.getName();
198             final Optional<Revision> rev = momb.getRevision();
199             final Map<Optional<Revision>, ModuleNodeImpl> revs = moduleGraph.row(name);
200             if (revs.containsKey(rev)) {
201                 throw new IllegalArgumentException(String.format("Module:%s with revision:%s declared twice", name,
202                     formatRevDate(rev)));
203             }
204
205             revs.put(rev, new ModuleNodeImpl(name, rev.orElse(null), momb));
206         }
207     }
208
209     private static String formatRevDate(final Optional<Revision> rev) {
210         return rev.map(Revision::toString).orElse("default");
211     }
212
213     private static final class ModuleNodeImpl extends NodeImpl {
214         private final String name;
215         private final Revision revision;
216         private final Module originalObject;
217
218         ModuleNodeImpl(final String name, final Revision revision, final Module module) {
219             this.name = name;
220             this.revision = revision;
221             originalObject = module;
222         }
223
224         String getName() {
225             return name;
226         }
227
228         Optional<Revision> getRevision() {
229             return Optional.ofNullable(revision);
230         }
231
232         @Override
233         public int hashCode() {
234             final int prime = 31;
235             int result = 1;
236             result = prime * result + Objects.hashCode(name);
237             result = prime * result + Objects.hashCode(revision);
238             return result;
239         }
240
241         @Override
242         public boolean equals(final Object obj) {
243             if (this == obj) {
244                 return true;
245             }
246             if (obj == null) {
247                 return false;
248             }
249             if (getClass() != obj.getClass()) {
250                 return false;
251             }
252             final ModuleNodeImpl other = (ModuleNodeImpl) obj;
253             if (!Objects.equals(name, other.name)) {
254                 return false;
255             }
256             if (!Objects.equals(revision, other.revision)) {
257                 return false;
258             }
259             return true;
260         }
261
262         @Override
263         public String toString() {
264             return "Module [name=" + name + ", revision=" + formatRevDate(getRevision()) + "]";
265         }
266
267         public Module getReference() {
268             return originalObject;
269         }
270     }
271 }