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