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