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