Merge "BUG-1605: Fix yang-binding interfaces"
[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  * This program and the accompanying materials are made available under the
4  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
5  * and is available at http://www.eclipse.org/legal/epl-v10.html
6  */
7 package org.opendaylight.yangtools.yang.parser.util;
8
9 import static java.util.Arrays.asList;
10
11 import com.google.common.annotations.VisibleForTesting;
12 import com.google.common.base.Function;
13 import com.google.common.base.Optional;
14 import com.google.common.collect.Lists;
15 import com.google.common.collect.Maps;
16 import com.google.common.collect.Sets;
17
18 import java.net.URI;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.Date;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Set;
27
28 import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
29 import org.opendaylight.yangtools.yang.model.api.Module;
30 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
31 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
32 import org.opendaylight.yangtools.yang.parser.builder.impl.ModuleBuilder;
33 import org.opendaylight.yangtools.yang.parser.util.TopologicalSort.Node;
34 import org.opendaylight.yangtools.yang.parser.util.TopologicalSort.NodeImpl;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 /**
39  * Creates a module dependency graph from provided {@link ModuleBuilder}s and
40  * provides a {@link #sort(ModuleBuilder...)} method. It is topological sort and
41  * returns modules in order in which they should be processed (e.g. if A imports
42  * B, sort returns {B, A}).
43  */
44 public final class ModuleDependencySort {
45
46     private static final Date DEFAULT_REVISION = new Date(0);
47     private static final Logger LOGGER = LoggerFactory.getLogger(ModuleDependencySort.class);
48     private static final Function<Node, Module> TOPOLOGY_FUNCTION = new Function<TopologicalSort.Node, Module>() {
49         @Override
50         public Module apply(final TopologicalSort.Node input) {
51             ModuleOrModuleBuilder moduleOrModuleBuilder = ((ModuleNodeImpl) input).getReference();
52             return moduleOrModuleBuilder.getModule();
53         }
54     };
55
56     /**
57      * It is not desirable to instance this class
58      */
59     private ModuleDependencySort() {
60     }
61
62
63     /**
64      * Extracts {@link ModuleBuilder} from a {@link ModuleNodeImpl}.
65      */
66     private static final Function<TopologicalSort.Node, ModuleBuilder> NODE_TO_MODULEBUILDER = new Function<TopologicalSort.Node, ModuleBuilder>() {
67         @Override
68         public ModuleBuilder apply(final TopologicalSort.Node input) {
69             // Cast to ModuleBuilder from Node and return
70             ModuleOrModuleBuilder moduleOrModuleBuilder = ((ModuleNodeImpl) input).getReference();
71             return moduleOrModuleBuilder.getModuleBuilder();
72         }
73     };
74
75     /**
76      * Topological sort of module builder dependency graph.
77      *
78      * @return Sorted list of Module builders. Modules can be further processed
79      *         in returned order.
80      */
81     public static List<ModuleBuilder> sort(final ModuleBuilder... builders) {
82         return sort(asList(builders));
83     }
84
85     public static List<ModuleBuilder> sort(final Collection<ModuleBuilder> builders) {
86         List<TopologicalSort.Node> sorted = sortInternal(ModuleOrModuleBuilder.fromAll(
87                 Collections.<Module>emptySet(),builders));
88         return Lists.transform(sorted, NODE_TO_MODULEBUILDER);
89     }
90
91     public static List<ModuleBuilder> sortWithContext(final SchemaContext context, final ModuleBuilder... builders) {
92         List<ModuleOrModuleBuilder> all = ModuleOrModuleBuilder.fromAll(context.getModules(), asList(builders));
93
94         List<TopologicalSort.Node> sorted = sortInternal(all);
95         // Cast to ModuleBuilder from Node if possible and return
96         return Lists.transform(sorted, new Function<TopologicalSort.Node, ModuleBuilder>() {
97
98             @Override
99             public ModuleBuilder apply(final TopologicalSort.Node input) {
100                 ModuleOrModuleBuilder moduleOrModuleBuilder = ((ModuleNodeImpl) input).getReference();
101                 if (moduleOrModuleBuilder.isModuleBuilder()) {
102                     return moduleOrModuleBuilder.getModuleBuilder();
103                 } else {
104                     return null;
105                 }
106             }
107         });
108     }
109
110     /**
111      * Topological sort of module dependency graph.
112      *
113      * @return Sorted list of Modules. Modules can be further processed in
114      *         returned order.
115      */
116     public static List<Module> sort(final Module... modules) {
117         List<TopologicalSort.Node> sorted = sortInternal(ModuleOrModuleBuilder.fromAll(asList(modules),
118                 Collections.<ModuleBuilder>emptyList()));
119         // Cast to Module from Node and return
120         return Lists.transform(sorted, TOPOLOGY_FUNCTION);
121     }
122
123     private static List<TopologicalSort.Node> sortInternal(final Iterable<ModuleOrModuleBuilder> modules) {
124         Map<String, Map<Date, ModuleNodeImpl>> moduleGraph = createModuleGraph(modules);
125
126         Set<TopologicalSort.Node> nodes = Sets.newHashSet();
127         for (Map<Date, ModuleNodeImpl> map : moduleGraph.values()) {
128             for (ModuleNodeImpl node : map.values()) {
129                 nodes.add(node);
130             }
131         }
132
133         return TopologicalSort.sort(nodes);
134     }
135
136     @VisibleForTesting
137     static Map<String, Map<Date, ModuleNodeImpl>> createModuleGraph(final Iterable<ModuleOrModuleBuilder> builders) {
138         Map<String, Map<Date, ModuleNodeImpl>> moduleGraph = Maps.newHashMap();
139
140         processModules(moduleGraph, builders);
141         processDependencies(moduleGraph, builders);
142
143         return moduleGraph;
144     }
145
146     /**
147      * Extract module:revision from module builders
148      */
149     private static void processDependencies(final Map<String, Map<Date, ModuleNodeImpl>> moduleGraph,
150             final Iterable<ModuleOrModuleBuilder> mmbs) {
151         Map<URI, Object> allNS = new HashMap<>();
152
153         // Create edges in graph
154         for (ModuleOrModuleBuilder mmb : mmbs) {
155             Map<String, Date> imported = Maps.newHashMap();
156
157             String fromName;
158             Date fromRevision;
159             Collection<ModuleImport> imports;
160             URI ns;
161
162             if (mmb.isModule()) {
163                 Module module = mmb.getModule();
164                 fromName = module.getName();
165                 fromRevision = module.getRevision();
166                 imports = module.getImports();
167                 ns = module.getNamespace();
168             } else {
169                 ModuleBuilder moduleBuilder = mmb.getModuleBuilder();
170                 fromName = moduleBuilder.getName();
171                 fromRevision = moduleBuilder.getRevision();
172                 imports = moduleBuilder.getImports().values();
173                 ns = moduleBuilder.getNamespace();
174             }
175
176             // check for existence of module with same namespace
177             if (allNS.containsKey(ns)) {
178                 Object mod = allNS.get(ns);
179                 String name = null;
180                 Date revision = null;
181
182                 if(mod instanceof ModuleOrModuleBuilder) {
183                     ModuleOrModuleBuilder modOrmodBuilder = ((ModuleOrModuleBuilder) mod);
184                     if(modOrmodBuilder.isModule()) {
185                         mod = ((ModuleOrModuleBuilder) mod).getModule();
186                     } else if (modOrmodBuilder.isModuleBuilder()) {
187                         mod = ((ModuleOrModuleBuilder) mod).getModuleBuilder();
188                     } else {
189                         LOGGER.warn("ModuleOrModuleBuilder is neither Module or ModuleBuilder");
190                     }
191                 }
192                 if (mod instanceof Module) {
193                     name = ((Module) mod).getName();
194                     revision = ((Module) mod).getRevision();
195                 } else if (mod instanceof ModuleBuilder) {
196                     name = ((ModuleBuilder) mod).getName();
197                     revision = ((ModuleBuilder) mod).getRevision();
198                 } else {
199                     LOGGER.warn("Module has no name: {}", mod);
200                 }
201                 if (!(fromName.equals(name))) {
202                     LOGGER.warn(
203                             "Error while sorting module [{}, {}]: module with same namespace ({}) already loaded: [{}, {}]",
204                             fromName, fromRevision, ns, name, revision);
205                 }
206             } else {
207                 allNS.put(ns, mmb);
208             }
209
210             // no need to check if other Type of object, check is performed in
211             // process modules
212
213             if (fromRevision == null) {
214                 fromRevision = DEFAULT_REVISION;
215             }
216
217             for (ModuleImport imprt : imports) {
218                 String toName = imprt.getModuleName();
219                 Date toRevision = imprt.getRevision() == null ? DEFAULT_REVISION : imprt.getRevision();
220
221                 ModuleNodeImpl from = moduleGraph.get(fromName).get(fromRevision);
222
223                 ModuleNodeImpl to = getModuleByNameAndRevision(moduleGraph, fromName, fromRevision, toName, toRevision);
224
225                 /*
226                  * Check imports: If module is imported twice with different
227                  * revisions then throw exception
228                  */
229                 if (imported.get(toName) != null && !imported.get(toName).equals(toRevision)
230                         && !imported.get(toName).equals(DEFAULT_REVISION) && !toRevision.equals(DEFAULT_REVISION)) {
231                     ex(String.format("Module:%s imported twice with different revisions:%s, %s", toName,
232                             formatRevDate(imported.get(toName)), formatRevDate(toRevision)));
233                 }
234
235                 imported.put(toName, toRevision);
236
237                 from.addEdge(to);
238             }
239         }
240     }
241
242     /**
243      * Get imported module by its name and revision from moduleGraph
244      */
245     private static ModuleNodeImpl getModuleByNameAndRevision(final Map<String, Map<Date, ModuleNodeImpl>> moduleGraph,
246             final String fromName, final Date fromRevision, final String toName, final Date toRevision) {
247         ModuleNodeImpl to = null;
248
249         if (moduleGraph.get(toName) == null || !moduleGraph.get(toName).containsKey(toRevision)) {
250             // If revision is not specified in import, but module exists
251             // with different revisions, take first
252             if (moduleGraph.get(toName) != null && !moduleGraph.get(toName).isEmpty()
253                     && toRevision.equals(DEFAULT_REVISION)) {
254                 to = moduleGraph.get(toName).values().iterator().next();
255                 LOGGER.trace(String
256                         .format("Import:%s:%s by module:%s:%s does not specify revision, using:%s:%s for module dependency sort",
257                                 toName, formatRevDate(toRevision), fromName, formatRevDate(fromRevision), to.getName(),
258                                 formatRevDate(to.getRevision())));
259             } else {
260                 LOGGER.warn(String.format("Not existing module imported:%s:%s by:%s:%s", toName,
261                         formatRevDate(toRevision), fromName, formatRevDate(fromRevision)));
262                 LOGGER.warn("Available models: {}", moduleGraph);
263                 ex(String.format("Not existing module imported:%s:%s by:%s:%s", toName, formatRevDate(toRevision),
264                         fromName, formatRevDate(fromRevision)));
265             }
266         } else {
267             to = moduleGraph.get(toName).get(toRevision);
268         }
269         return to;
270     }
271
272     private static void ex(final String message) {
273         throw new YangValidationException(message);
274     }
275
276     /**
277      * Extract dependencies from module builders or modules to fill dependency
278      * graph
279      */
280     private static void processModules(final Map<String, Map<Date, ModuleNodeImpl>> moduleGraph,
281             final Iterable<ModuleOrModuleBuilder> builders) {
282
283         // Process nodes
284         for (ModuleOrModuleBuilder momb : builders) {
285
286             String name;
287             Date rev;
288
289             if (momb.isModule()) {
290                 name = momb.getModule().getName();
291                 rev = momb.getModule().getRevision();
292             } else {
293                 name = momb.getModuleBuilder().getName();
294                 rev = momb.getModuleBuilder().getRevision();
295             }
296
297             if (rev == null) {
298                 rev = DEFAULT_REVISION;
299             }
300
301             if (moduleGraph.get(name) == null) {
302                 moduleGraph.put(name, Maps.<Date, ModuleNodeImpl> newHashMap());
303             }
304
305             if (moduleGraph.get(name).get(rev) != null) {
306                 ex(String.format("Module:%s with revision:%s declared twice", name, formatRevDate(rev)));
307             }
308
309             moduleGraph.get(name).put(rev, new ModuleNodeImpl(name, rev, momb));
310         }
311     }
312
313     private static String formatRevDate(final Date rev) {
314         return rev.equals(DEFAULT_REVISION) ? "default" : SimpleDateFormatUtil.getRevisionFormat().format(rev);
315     }
316
317     @VisibleForTesting
318     static class ModuleNodeImpl extends NodeImpl {
319         private final String name;
320         private final Date revision;
321         private final ModuleOrModuleBuilder originalObject;
322
323         public ModuleNodeImpl(final String name, final Date revision, final ModuleOrModuleBuilder builder) {
324             this.name = name;
325             this.revision = revision;
326             this.originalObject = builder;
327         }
328
329         public String getName() {
330             return name;
331         }
332
333         public Date getRevision() {
334             return revision;
335         }
336
337         @Override
338         public int hashCode() {
339             final int prime = 31;
340             int result = 1;
341             result = prime * result + ((name == null) ? 0 : name.hashCode());
342             result = prime * result + ((revision == null) ? 0 : revision.hashCode());
343             return result;
344         }
345
346         @Override
347         public boolean equals(final Object obj) {
348             if (this == obj) {
349                 return true;
350             }
351             if (obj == null) {
352                 return false;
353             }
354             if (getClass() != obj.getClass()) {
355                 return false;
356             }
357             ModuleNodeImpl other = (ModuleNodeImpl) obj;
358             if (name == null) {
359                 if (other.name != null) {
360                     return false;
361                 }
362             } else if (!name.equals(other.name)) {
363                 return false;
364             }
365             if (revision == null) {
366                 if (other.revision != null) {
367                     return false;
368                 }
369             } else if (!revision.equals(other.revision)) {
370                 return false;
371             }
372             return true;
373         }
374
375         @Override
376         public String toString() {
377             return "Module [name=" + name + ", revision=" + formatRevDate(revision) + "]";
378         }
379
380         public ModuleOrModuleBuilder getReference() {
381             return originalObject;
382         }
383
384     }
385
386 }
387 class ModuleOrModuleBuilder {
388     private final Optional<Module> maybeModule;
389     private final Optional<ModuleBuilder> maybeModuleBuilder;
390
391     ModuleOrModuleBuilder(final Module module) {
392         maybeModule = Optional.of(module);
393         maybeModuleBuilder = Optional.absent();
394     }
395
396     ModuleOrModuleBuilder(final ModuleBuilder moduleBuilder) {
397         maybeModule = Optional.absent();
398         maybeModuleBuilder = Optional.of(moduleBuilder);
399     }
400     boolean isModule(){
401         return maybeModule.isPresent();
402     }
403     boolean isModuleBuilder(){
404         return maybeModuleBuilder.isPresent();
405     }
406     Module getModule(){
407         return maybeModule.get();
408     }
409     ModuleBuilder getModuleBuilder(){
410         return maybeModuleBuilder.get();
411     }
412
413     static List<ModuleOrModuleBuilder> fromAll(final Collection<Module> modules, final Collection<ModuleBuilder> moduleBuilders) {
414         List<ModuleOrModuleBuilder> result = new ArrayList<>(modules.size() + moduleBuilders.size());
415         for(Module m: modules){
416             result.add(new ModuleOrModuleBuilder(m));
417         }
418         for (ModuleBuilder mb : moduleBuilders) {
419             result.add(new ModuleOrModuleBuilder(mb));
420         }
421         return result;
422     }
423 }