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.spi;
10 import com.google.common.annotations.Beta;
11 import com.google.common.collect.HashBasedTable;
12 import com.google.common.collect.Lists;
13 import com.google.common.collect.Table;
14 import java.util.Arrays;
15 import java.util.Collection;
16 import java.util.HashMap;
17 import java.util.HashSet;
18 import java.util.LinkedHashSet;
19 import java.util.List;
21 import java.util.Objects;
22 import java.util.Optional;
23 import org.opendaylight.yangtools.util.TopologicalSort;
24 import org.opendaylight.yangtools.util.TopologicalSort.Node;
25 import org.opendaylight.yangtools.util.TopologicalSort.NodeImpl;
26 import org.opendaylight.yangtools.yang.common.Revision;
27 import org.opendaylight.yangtools.yang.common.XMLNamespace;
28 import org.opendaylight.yangtools.yang.common.YangVersion;
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.Submodule;
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);
44 private ModuleDependencySort() {
49 * Topological sort of module dependency graph.
51 * @param modules YANG modules
52 * @return Sorted list of Modules. Modules can be further processed in returned order.
53 * @throws IllegalArgumentException when provided modules are not consistent.
55 public static List<Module> sort(final Module... modules) {
56 return sort(Arrays.asList(modules));
60 * Topological sort of module dependency graph.
62 * @param modules YANG modules
63 * @return Sorted list of Modules. Modules can be further processed in returned order.
64 * @throws IllegalArgumentException when provided modules are not consistent.
66 public static List<Module> sort(final Collection<? extends Module> modules) {
67 final List<Node> sorted = sortInternal(modules);
68 // Cast to Module from Node and return
69 return Lists.transform(sorted, input -> input == null ? null : ((ModuleNodeImpl) input).getReference());
72 private static List<Node> sortInternal(final Collection<? extends Module> modules) {
73 final Table<String, Optional<Revision>, ModuleNodeImpl> moduleGraph = createModuleGraph(modules);
74 return TopologicalSort.sort(new HashSet<>(moduleGraph.values()));
77 private static Table<String, Optional<Revision>, ModuleNodeImpl> createModuleGraph(
78 final Collection<? extends Module> builders) {
79 final Table<String, Optional<Revision>, ModuleNodeImpl> moduleGraph = HashBasedTable.create();
81 processModules(moduleGraph, builders);
82 processDependencies(moduleGraph, builders);
88 * Extract module:revision from modules.
90 private static void processDependencies(final Table<String, Optional<Revision>, ModuleNodeImpl> moduleGraph,
91 final Collection<? extends Module> mmbs) {
92 final Map<XMLNamespace, Module> allNS = new HashMap<>();
94 // Create edges in graph
95 for (final Module module : mmbs) {
96 final Map<String, Optional<Revision>> imported = new HashMap<>();
97 final String fromName = module.getName();
98 final XMLNamespace ns = module.getNamespace();
99 final Optional<Revision> fromRevision = module.getRevision();
101 // check for existence of module with same namespace
102 final Module prev = allNS.putIfAbsent(ns, module);
104 final String name = prev.getName();
105 if (!fromName.equals(name)) {
106 LOG.warn("Error while sorting module [{}, {}]: module with same namespace ({}) already loaded:"
107 + " [{}, {}]", fromName, fromRevision, ns, name, prev.getRevision());
111 // no need to check if other Type of object, check is performed in process modules
112 for (final ModuleImport imprt : allImports(module)) {
113 final String toName = imprt.getModuleName().getLocalName();
114 final Optional<Revision> toRevision = imprt.getRevision();
116 final ModuleNodeImpl from = moduleGraph.get(fromName, fromRevision);
117 final ModuleNodeImpl to = getModuleByNameAndRevision(moduleGraph, fromName, fromRevision, toName,
121 * If it is an yang 1 module, check imports: If module is imported twice with different
122 * revisions then throw exception
124 if (module.getYangVersion() == YangVersion.VERSION_1) {
125 final Optional<Revision> impRevision = imported.get(toName);
126 if (impRevision != null && impRevision.isPresent() && !impRevision.equals(toRevision)
127 && toRevision.isPresent()) {
128 throw new IllegalArgumentException(String.format(
129 "Module:%s imported twice with different revisions:%s, %s", toName,
130 formatRevDate(impRevision), formatRevDate(toRevision)));
134 imported.put(toName, toRevision);
141 private static Collection<? extends ModuleImport> allImports(final Module mod) {
142 if (mod.getSubmodules().isEmpty()) {
143 return mod.getImports();
146 final Collection<ModuleImport> concat = new LinkedHashSet<>();
147 concat.addAll(mod.getImports());
148 for (Submodule sub : mod.getSubmodules()) {
149 concat.addAll(sub.getImports());
155 * Get imported module by its name and revision from moduleGraph.
157 private static ModuleNodeImpl getModuleByNameAndRevision(
158 final Table<String, Optional<Revision>, ModuleNodeImpl> moduleGraph, final String fromName,
159 final Optional<Revision> fromRevision, final String toName, final Optional<Revision> toRevision) {
161 final ModuleNodeImpl exact = moduleGraph.get(toName, toRevision);
166 // If revision is not specified in import, but module exists with different revisions, take first one
167 if (toRevision.isEmpty()) {
168 final Map<Optional<Revision>, ModuleNodeImpl> modulerevs = moduleGraph.row(toName);
170 if (!modulerevs.isEmpty()) {
171 final ModuleNodeImpl first = modulerevs.values().iterator().next();
172 if (LOG.isTraceEnabled()) {
173 LOG.trace("Import:{}:{} by module:{}:{} does not specify revision, using:{}:{}"
174 + " for module dependency sort", toName, formatRevDate(toRevision), fromName,
175 formatRevDate(fromRevision), first.getName(), formatRevDate(first.getRevision()));
181 LOG.warn("Not existing module imported:{}:{} by:{}:{}", toName, formatRevDate(toRevision), fromName,
182 formatRevDate(fromRevision));
183 LOG.warn("Available models: {}", moduleGraph);
184 throw new IllegalArgumentException(String.format("Not existing module imported:%s:%s by:%s:%s", toName,
185 formatRevDate(toRevision), fromName, formatRevDate(fromRevision)));
189 * Extract dependencies from modules to fill dependency graph.
191 private static void processModules(final Table<String, Optional<Revision>, ModuleNodeImpl> moduleGraph,
192 final Iterable<? extends Module> modules) {
195 for (final Module momb : modules) {
197 final String name = momb.getName();
198 final Optional<Revision> rev = momb.getRevision();
199 final Map<Optional<Revision>, ModuleNodeImpl> revs = moduleGraph.row(name);
200 if (revs.containsKey(rev)) {
201 throw new IllegalArgumentException(String.format("Module:%s with revision:%s declared twice", name,
202 formatRevDate(rev)));
205 revs.put(rev, new ModuleNodeImpl(name, rev.orElse(null), momb));
209 private static String formatRevDate(final Optional<Revision> rev) {
210 return rev.map(Revision::toString).orElse("default");
213 private static final class ModuleNodeImpl extends NodeImpl {
214 private final String name;
215 private final Revision revision;
216 private final Module originalObject;
218 ModuleNodeImpl(final String name, final Revision revision, final Module module) {
220 this.revision = revision;
221 originalObject = module;
228 Optional<Revision> getRevision() {
229 return Optional.ofNullable(revision);
233 public int hashCode() {
234 final int prime = 31;
236 result = prime * result + Objects.hashCode(name);
237 result = prime * result + Objects.hashCode(revision);
242 public boolean equals(final Object obj) {
249 if (getClass() != obj.getClass()) {
252 final ModuleNodeImpl other = (ModuleNodeImpl) obj;
253 if (!Objects.equals(name, other.name)) {
256 if (!Objects.equals(revision, other.revision)) {
263 public String toString() {
264 return "Module [name=" + name + ", revision=" + formatRevDate(getRevision()) + "]";
267 public Module getReference() {
268 return originalObject;