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