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