2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.yangtools.yang.model.util;
10 import static org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil.DEFAULT_DATE_REV;
12 import com.google.common.annotations.Beta;
13 import com.google.common.collect.HashBasedTable;
14 import com.google.common.collect.Lists;
15 import com.google.common.collect.Table;
17 import java.util.Arrays;
18 import java.util.Collection;
19 import java.util.Date;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.List;
24 import java.util.Objects;
25 import org.opendaylight.yangtools.util.TopologicalSort;
26 import org.opendaylight.yangtools.util.TopologicalSort.Node;
27 import org.opendaylight.yangtools.util.TopologicalSort.NodeImpl;
28 import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
29 import org.opendaylight.yangtools.yang.common.YangVersion;
30 import org.opendaylight.yangtools.yang.model.api.Module;
31 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
36 * Creates a module dependency graph from provided {@link Module}s and provides a {@link #sort(Collection)} method.
37 * It is topological sort and returns modules in order in which they should be processed (e.g. if A imports B, sort
41 public final class ModuleDependencySort {
42 private static final Logger LOG = LoggerFactory.getLogger(ModuleDependencySort.class);
45 * It is not desirable to instance this class.
47 private ModuleDependencySort() {
48 throw new UnsupportedOperationException();
52 * Topological sort of module dependency graph.
54 * @param modules YANG modules
55 * @return Sorted list of Modules. Modules can be further processed in returned order.
56 * @throws IllegalArgumentException when provided modules are not consistent.
58 public static List<Module> sort(final Module... modules) {
59 return sort(Arrays.asList(modules));
63 * Topological sort of module dependency graph.
65 * @param modules YANG modules
66 * @return Sorted list of Modules. Modules can be further processed in returned order.
67 * @throws IllegalArgumentException when provided modules are not consistent.
69 public static List<Module> sort(final Collection<Module> modules) {
70 return sort((Iterable<Module>)modules);
74 * Topological sort of module dependency graph.
76 * @param modules YANG modules
77 * @return Sorted list of Modules. Modules can be further processed in
79 * @throws IllegalArgumentException when provided modules are not consistent.
81 * @deprecated Use {@link #sort(Collection)} instead.
84 public static List<Module> sort(final Iterable<Module> modules) {
85 final List<Node> sorted = sortInternal(modules);
86 // Cast to Module from Node and return
87 return Lists.transform(sorted, input -> input == null ? null : ((ModuleNodeImpl) input).getReference());
90 private static List<Node> sortInternal(final Iterable<Module> modules) {
91 final Table<String, Date, ModuleNodeImpl> moduleGraph = createModuleGraph(modules);
92 return TopologicalSort.sort(new HashSet<>(moduleGraph.values()));
95 private static Table<String, Date, ModuleNodeImpl> createModuleGraph(final Iterable<Module> builders) {
96 final Table<String, Date, ModuleNodeImpl> moduleGraph = HashBasedTable.create();
98 processModules(moduleGraph, builders);
99 processDependencies(moduleGraph, builders);
105 * Extract module:revision from modules.
107 private static void processDependencies(final Table<String, Date, ModuleNodeImpl> moduleGraph,
108 final Iterable<Module> mmbs) {
109 final Map<URI, Module> allNS = new HashMap<>();
111 // Create edges in graph
112 for (final Module module : mmbs) {
113 final Map<String, Date> imported = new HashMap<>();
114 final String fromName = module.getName();
115 final URI ns = module.getNamespace();
116 Date fromRevision = module.getRevision();
118 // check for existence of module with same namespace
119 final Module prev = allNS.putIfAbsent(ns, module);
121 final String name = prev.getName();
122 if (!fromName.equals(name)) {
123 LOG.warn("Error while sorting module [{}, {}]: module with same namespace ({}) already loaded:"
124 + " [{}, {}]", fromName, fromRevision, ns, name, prev.getRevision());
128 // no need to check if other Type of object, check is performed in
131 if (fromRevision == null) {
132 fromRevision = DEFAULT_DATE_REV;
135 for (final ModuleImport imprt : module.getImports()) {
136 final String toName = imprt.getModuleName();
137 final Date toRevision = imprt.getRevision() == null ? DEFAULT_DATE_REV : imprt.getRevision();
139 final ModuleNodeImpl from = moduleGraph.get(fromName, fromRevision);
141 final ModuleNodeImpl to = getModuleByNameAndRevision(moduleGraph, fromName, fromRevision, toName,
145 * If it is an yang 1 module, check imports: If module is imported twice with different
146 * revisions then throw exception
148 if (YangVersion.VERSION_1.toString().equals(module.getYangVersion())) {
149 final Date impRevision = imported.get(toName);
150 if (impRevision != null && !impRevision.equals(toRevision)
151 && !DEFAULT_DATE_REV.equals(impRevision) && !DEFAULT_DATE_REV.equals(toRevision)) {
152 throw new IllegalArgumentException(String.format(
153 "Module:%s imported twice with different revisions:%s, %s", toName,
154 formatRevDate(impRevision), formatRevDate(toRevision)));
158 imported.put(toName, toRevision);
166 * Get imported module by its name and revision from moduleGraph.
168 private static ModuleNodeImpl getModuleByNameAndRevision(final Table<String, Date, ModuleNodeImpl> moduleGraph,
169 final String fromName, final Date fromRevision, final String toName, final Date toRevision) {
171 final ModuleNodeImpl exact = moduleGraph.get(toName, toRevision);
176 // If revision is not specified in import, but module exists with different revisions, take first one
177 if (DEFAULT_DATE_REV.equals(toRevision)) {
178 final Map<Date, ModuleNodeImpl> modulerevs = moduleGraph.row(toName);
180 if (!modulerevs.isEmpty()) {
181 final ModuleNodeImpl first = modulerevs.values().iterator().next();
182 if (LOG.isTraceEnabled()) {
183 LOG.trace("Import:{}:{} by module:{}:{} does not specify revision, using:{}:{}"
184 + " for module dependency sort", toName, formatRevDate(toRevision), fromName,
185 formatRevDate(fromRevision), first.getName(), formatRevDate(first.getRevision()));
191 LOG.warn("Not existing module imported:{}:{} by:{}:{}", toName, formatRevDate(toRevision), fromName,
192 formatRevDate(fromRevision));
193 LOG.warn("Available models: {}", moduleGraph);
194 throw new IllegalArgumentException(String.format("Not existing module imported:%s:%s by:%s:%s", toName,
195 formatRevDate(toRevision), fromName, formatRevDate(fromRevision)));
199 * Extract dependencies from modules to fill dependency graph.
201 private static void processModules(final Table<String, Date, ModuleNodeImpl> moduleGraph,
202 final Iterable<Module> modules) {
205 for (final Module momb : modules) {
207 final String name = momb.getName();
208 Date rev = momb.getRevision();
210 rev = DEFAULT_DATE_REV;
213 final Map<Date, ModuleNodeImpl> revs = moduleGraph.row(name);
214 if (revs.containsKey(rev)) {
215 throw new IllegalArgumentException(String.format("Module:%s with revision:%s declared twice", name,
216 formatRevDate(rev)));
219 revs.put(rev, new ModuleNodeImpl(name, rev, momb));
223 private static String formatRevDate(final Date rev) {
224 return rev.equals(DEFAULT_DATE_REV) ? "default" : SimpleDateFormatUtil.getRevisionFormat().format(rev);
227 private static final class ModuleNodeImpl extends NodeImpl {
228 private final String name;
229 private final Date revision;
230 private final Module originalObject;
232 ModuleNodeImpl(final String name, final Date revision, final Module module) {
234 this.revision = revision;
235 this.originalObject = module;
247 public int hashCode() {
248 final int prime = 31;
250 result = prime * result + Objects.hashCode(name);
251 result = prime * result + Objects.hashCode(revision);
256 public boolean equals(final Object obj) {
263 if (getClass() != obj.getClass()) {
266 final ModuleNodeImpl other = (ModuleNodeImpl) obj;
268 if (other.name != null) {
271 } else if (!name.equals(other.name)) {
274 if (revision == null) {
275 if (other.revision != null) {
278 } else if (!revision.equals(other.revision)) {
285 public String toString() {
286 return "Module [name=" + name + ", revision=" + formatRevDate(revision) + "]";
289 public Module getReference() {
290 return originalObject;