2 * Copyright (c) 2015 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 com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
13 import com.google.common.base.Preconditions;
14 import com.google.common.base.Strings;
15 import com.google.common.collect.Collections2;
16 import com.google.common.collect.ImmutableMap;
17 import com.google.common.collect.ImmutableSet;
18 import com.google.common.collect.ImmutableSet.Builder;
19 import com.google.common.collect.ImmutableSetMultimap;
20 import com.google.common.collect.Maps;
21 import com.google.common.collect.Multimap;
22 import com.google.common.collect.Multimaps;
23 import com.google.common.collect.SetMultimap;
24 import com.google.common.collect.TreeMultimap;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.LinkedList;
29 import java.util.List;
31 import java.util.Optional;
33 import java.util.TreeMap;
34 import java.util.function.Function;
35 import org.eclipse.jdt.annotation.NonNull;
36 import org.opendaylight.yangtools.yang.common.QNameModule;
37 import org.opendaylight.yangtools.yang.common.Revision;
38 import org.opendaylight.yangtools.yang.common.XMLNamespace;
39 import org.opendaylight.yangtools.yang.model.api.Module;
40 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
41 import org.opendaylight.yangtools.yang.model.api.ModuleLike;
42 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
43 import org.opendaylight.yangtools.yang.model.api.Submodule;
45 public final class FilteringSchemaContextProxy extends AbstractSchemaContext {
46 private final ImmutableMap<QNameModule, Module> moduleMap;
48 //collection to be filled with filtered modules
49 private final ImmutableSet<Module> filteredModules;
51 //collections to be filled in with filtered data
52 private final ImmutableSetMultimap<XMLNamespace, Module> namespaceToModules;
53 private final ImmutableSetMultimap<String, Module> nameToModules;
56 * Filters SchemaContext for yang modules.
58 * @param delegate original SchemaContext
59 * @param rootModules modules (yang schemas) to be available and all their dependencies (modules importing
60 * rootModule and whole chain of their imports)
61 * @param additionalModuleIds (additional) modules (yang schemas) to be available and whole chain of their imports
63 public FilteringSchemaContextProxy(final SchemaContext delegate, final Collection<ModuleId> rootModules,
64 final Set<ModuleId> additionalModuleIds) {
65 requireNonNull(rootModules, "Base modules cannot be null.");
66 requireNonNull(additionalModuleIds, "Additional modules cannot be null.");
68 final Builder<Module> filteredModulesBuilder = new Builder<>();
70 // preparing map to get all modules with one name but difference in revision
71 final TreeMultimap<String, Module> nameToModulesAll = TreeMultimap.create(String::compareTo,
74 nameToModulesAll.putAll(getStringModuleMap(delegate));
76 // in case there is a particular dependency to view filteredModules/YANG models dependency is checked
77 // for module name and imports
78 processForRootModules(delegate, rootModules, filteredModulesBuilder);
80 // adding additional modules
81 processForAdditionalModules(delegate, additionalModuleIds, filteredModulesBuilder);
83 filteredModulesBuilder.addAll(getImportedModules(
84 Maps.uniqueIndex(delegate.getModules(), ModuleId.MODULE_TO_MODULE_ID::apply),
85 filteredModulesBuilder.build(), nameToModulesAll));
88 * Instead of doing this on each invocation of getModules(), pre-compute it once and keep it around.
90 final List<Module> sortedModules = new ArrayList<>(filteredModulesBuilder.build());
91 sortedModules.sort(NAME_REVISION_COMPARATOR);
92 this.filteredModules = ImmutableSet.copyOf(sortedModules);
94 final SetMultimap<XMLNamespace, Module> nsMap = Multimaps.newSetMultimap(new TreeMap<>(),
95 AbstractSchemaContext::createModuleSet);
96 final SetMultimap<String, Module> nameMap = Multimaps.newSetMultimap(new TreeMap<>(),
97 AbstractSchemaContext::createModuleSet);
98 final ImmutableMap.Builder<QNameModule, Module> moduleMapBuilder = ImmutableMap.builder();
99 for (final Module module : filteredModules) {
100 nameMap.put(module.getName(), module);
101 nsMap.put(module.getNamespace(), module);
102 moduleMapBuilder.put(module.getQNameModule(), module);
105 namespaceToModules = ImmutableSetMultimap.copyOf(nsMap);
106 nameToModules = ImmutableSetMultimap.copyOf(nameMap);
107 moduleMap = moduleMapBuilder.build();
110 private static void processForAdditionalModules(final SchemaContext delegate,
111 final Set<ModuleId> additionalModuleIds, final Builder<Module> filteredModulesBuilder) {
112 filteredModulesBuilder.addAll(Collections2.filter(delegate.getModules(),
113 module -> selectAdditionalModules(module, additionalModuleIds)));
116 private void processForRootModules(final SchemaContext delegate, final Collection<ModuleId> rootModules,
117 final Builder<Module> filteredModulesBuilder) {
118 filteredModulesBuilder.addAll(Collections2.filter(delegate.getModules(),
119 module -> checkModuleDependency(module, rootModules)));
122 private static Multimap<String, ? extends Module> getStringModuleMap(final SchemaContext delegate) {
123 return Multimaps.index(delegate.getModules(), Module::getName);
126 //dealing with imported module other than root and directly importing root
127 private static Collection<Module> getImportedModules(final Map<ModuleId, ? extends @NonNull Module> allModules,
128 final Collection<? extends @NonNull Module> baseModules,
129 final TreeMultimap<String, Module> nameToModulesAll) {
131 List<Module> relatedModules = new LinkedList<>();
133 for (Module module : baseModules) {
134 for (ModuleImport moduleImport : module.getImports()) {
135 Optional<Revision> revisionDate = moduleImport.getRevision();
136 if (revisionDate.isEmpty()) {
137 revisionDate = nameToModulesAll.get(moduleImport.getModuleName()).first().getRevision();
140 ModuleId key = new ModuleId(moduleImport.getModuleName(), revisionDate);
141 Module importedModule = allModules.get(key);
143 Preconditions.checkArgument(importedModule != null,
144 "Invalid schema, cannot find imported module: %s from module: %s, %s, modules:%s", key,
145 module.getQNameModule(), module.getName(), allModules);
146 relatedModules.add(importedModule);
148 //calling imports recursive
149 relatedModules.addAll(getImportedModules(allModules, Collections.singleton(importedModule),
154 return relatedModules;
158 public Set<Module> getModules() {
159 return filteredModules;
163 protected Map<QNameModule, Module> getModuleMap() {
168 protected SetMultimap<XMLNamespace, Module> getNamespaceToModules() {
169 return namespaceToModules;
173 protected SetMultimap<String, Module> getNameToModules() {
174 return nameToModules;
177 private static boolean selectAdditionalModules(final Module module, final Set<ModuleId> additionalModules) {
178 return additionalModules.contains(new ModuleId(module.getName(), module.getRevision()));
181 //check for any dependency regarding given string
182 private boolean checkModuleDependency(final ModuleLike module, final Collection<ModuleId> rootModules) {
183 for (ModuleId rootModule : rootModules) {
184 if (rootModule.equals(new ModuleId(module.getName(), module.getRevision()))) {
188 //handling/checking imports regarding root modules
189 for (ModuleImport moduleImport : module.getImports()) {
190 if (moduleImport.getModuleName().equals(rootModule.getName())) {
191 return moduleImport.getRevision().isEmpty()
192 || moduleImport.getRevision().equals(rootModule.getRev());
196 //submodules handling
197 for (Submodule moduleSub : module.getSubmodules()) {
198 return checkModuleDependency(moduleSub, rootModules);
206 public String toString() {
207 return String.format("SchemaContextProxyImpl{filteredModules=%s}", filteredModules);
210 public static final class ModuleId {
211 private final String name;
212 private final Revision rev;
214 public ModuleId(final String name, final Optional<Revision> rev) {
215 checkArgument(!Strings.isNullOrEmpty(name), "No module dependency name given. Nothing to do.");
217 checkArgument(rev.isPresent(), "No revision date given. Nothing to do.");
218 this.rev = rev.get();
221 public String getName() {
225 public Optional<Revision> getRev() {
226 return Optional.ofNullable(rev);
229 public static final Function<Module, ModuleId> MODULE_TO_MODULE_ID = input -> new ModuleId(input.getName(),
230 input.getRevision());
233 public boolean equals(final Object obj) {
237 if (!(obj instanceof ModuleId)) {
241 ModuleId moduleId = (ModuleId) obj;
242 if (name != null ? !name.equals(moduleId.name) : moduleId.name != null) {
245 if (rev != null ? !rev.equals(moduleId.rev) : moduleId.rev != null) {
253 public int hashCode() {
254 int result = name != null ? name.hashCode() : 0;
255 result = 31 * result + (rev != null ? rev.hashCode() : 0);
260 public String toString() {
261 return String.format("ModuleId{name='%s', rev=%s}",name,rev);