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