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