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