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