f51462b354d8273af0c1fe4794daae6932fb81d4
[yangtools.git] / yang / yang-model-util / src / main / java / org / opendaylight / yangtools / yang / model / util / FilteringSchemaContextProxy.java
1 /*
2  * Copyright (c) 2015 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
9 package org.opendaylight.yangtools.yang.model.util;
10
11 import com.google.common.base.Preconditions;
12 import com.google.common.base.Strings;
13 import com.google.common.collect.Collections2;
14 import com.google.common.collect.ImmutableMap;
15 import com.google.common.collect.ImmutableSet.Builder;
16 import com.google.common.collect.ImmutableSetMultimap;
17 import com.google.common.collect.Lists;
18 import com.google.common.collect.Maps;
19 import com.google.common.collect.Multimap;
20 import com.google.common.collect.Multimaps;
21 import com.google.common.collect.SetMultimap;
22 import com.google.common.collect.TreeMultimap;
23 import java.net.URI;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Optional;
29 import java.util.Set;
30 import java.util.TreeMap;
31 import java.util.function.Function;
32 import javax.annotation.concurrent.Immutable;
33 import org.opendaylight.yangtools.yang.common.Revision;
34 import org.opendaylight.yangtools.yang.model.api.Module;
35 import org.opendaylight.yangtools.yang.model.api.ModuleIdentifier;
36 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
37 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
38
39 @Immutable
40 public final class FilteringSchemaContextProxy extends AbstractSchemaContext {
41
42     //collection to be filled with filtered modules
43     private final Set<Module> filteredModules;
44
45     //collections to be filled in with filtered data
46     private final SetMultimap<URI, Module> namespaceToModules;
47     private final SetMultimap<String, Module> nameToModules;
48
49     /**
50      * Filters SchemaContext for yang modules.
51      *
52      * @param delegate original SchemaContext
53      * @param rootModules modules (yang schemas) to be available and all their dependencies (modules importing
54      *                    rootModule and whole chain of their imports)
55      * @param additionalModuleIds (additional) modules (yang schemas) to be available and whole chain of their imports
56      */
57     public FilteringSchemaContextProxy(final SchemaContext delegate, final Collection<ModuleId> rootModules,
58             final Set<ModuleId> additionalModuleIds) {
59         Preconditions.checkNotNull(rootModules, "Base modules cannot be null.");
60         Preconditions.checkNotNull(additionalModuleIds, "Additional modules cannot be null.");
61
62         final Builder<Module> filteredModulesBuilder = new Builder<>();
63         final SetMultimap<URI, Module> nsMap = Multimaps.newSetMultimap(new TreeMap<>(),
64             AbstractSchemaContext::createModuleSet);
65         final SetMultimap<String, Module> nameMap = Multimaps.newSetMultimap(new TreeMap<>(),
66             AbstractSchemaContext::createModuleSet);
67
68         ImmutableMap.Builder<ModuleIdentifier, String> identifiersToSourcesBuilder = ImmutableMap.builder();
69
70         //preparing map to get all modules with one name but difference in revision
71         final TreeMultimap<String, Module> nameToModulesAll = getStringModuleTreeMultimap();
72
73         nameToModulesAll.putAll(getStringModuleMap(delegate));
74
75         //in case there is a particular dependancy to view filteredModules/yang models
76         //dependancy is checked for module name and imports
77         processForRootModules(delegate, rootModules, filteredModulesBuilder);
78
79         //adding additional modules
80         processForAdditionalModules(delegate, additionalModuleIds, filteredModulesBuilder);
81
82         filteredModulesBuilder.addAll(getImportedModules(
83                 Maps.uniqueIndex(delegate.getModules(), ModuleId.MODULE_TO_MODULE_ID::apply),
84                 filteredModulesBuilder.build(), nameToModulesAll));
85
86         /**
87          * Instead of doing this on each invocation of getModules(), pre-compute
88          * it once and keep it around -- better than the set we got in.
89          */
90         this.filteredModules = filteredModulesBuilder.build();
91
92         for (final Module module : filteredModules) {
93             nameMap.put(module.getName(), module);
94             nsMap.put(module.getNamespace(), module);
95         }
96
97         namespaceToModules = ImmutableSetMultimap.copyOf(nsMap);
98         nameToModules = ImmutableSetMultimap.copyOf(nameMap);
99     }
100
101     private static TreeMultimap<String, Module> getStringModuleTreeMultimap() {
102         return TreeMultimap.create(String::compareTo, REVISION_COMPARATOR);
103     }
104
105     private static void processForAdditionalModules(final SchemaContext delegate,
106             final Set<ModuleId> additionalModuleIds, final Builder<Module> filteredModulesBuilder) {
107         filteredModulesBuilder.addAll(Collections2.filter(delegate.getModules(),
108             module -> selectAdditionalModules(module, additionalModuleIds)));
109     }
110
111     private void processForRootModules(final SchemaContext delegate, final Collection<ModuleId> rootModules,
112             final Builder<Module> filteredModulesBuilder) {
113         filteredModulesBuilder.addAll(Collections2.filter(delegate.getModules(),
114             module -> checkModuleDependency(module, rootModules)));
115     }
116
117     private static Multimap<String, Module> getStringModuleMap(final SchemaContext delegate) {
118         return Multimaps.index(delegate.getModules(), Module::getName);
119     }
120
121     //dealing with imported module other than root and directly importing root
122     private static Collection<Module> getImportedModules(final Map<ModuleId, Module> allModules,
123             final Set<Module> baseModules, final TreeMultimap<String, Module> nameToModulesAll) {
124
125         List<Module> relatedModules = Lists.newLinkedList();
126
127         for (Module module : baseModules) {
128             for (ModuleImport moduleImport : module.getImports()) {
129                 Optional<Revision> revisionDate = moduleImport.getRevision();
130                 if (!revisionDate.isPresent()) {
131                     revisionDate = nameToModulesAll.get(moduleImport.getModuleName()).last().getRevision();
132                 }
133
134                 ModuleId key = new ModuleId(moduleImport.getModuleName(), revisionDate);
135                 Module importedModule = allModules.get(key);
136
137                 Preconditions.checkArgument(importedModule != null,
138                         "Invalid schema, cannot find imported module: %s from module: %s, %s, modules:%s", key,
139                         module.getQNameModule(), module.getName(), allModules);
140                 relatedModules.add(importedModule);
141
142                 //calling imports recursive
143                 relatedModules.addAll(getImportedModules(allModules, Collections.singleton(importedModule),
144                             nameToModulesAll));
145             }
146         }
147
148         return relatedModules;
149     }
150
151     @Override
152     public Set<Module> getModules() {
153         return filteredModules;
154     }
155
156     @Override
157     protected SetMultimap<URI, Module> getNamespaceToModules() {
158         return namespaceToModules;
159     }
160
161     @Override
162     protected SetMultimap<String, Module> getNameToModules() {
163         return nameToModules;
164     }
165
166     private static boolean selectAdditionalModules(final Module module, final Set<ModuleId> additionalModules) {
167         return additionalModules.contains(new ModuleId(module.getName(), module.getRevision()));
168     }
169
170     //check for any dependency regarding given string
171     private boolean checkModuleDependency(final Module module, final Collection<ModuleId> rootModules) {
172
173         for (ModuleId rootModule : rootModules) {
174
175             if (rootModule.equals(new ModuleId(module.getName(), module.getRevision()))) {
176                 return true;
177             }
178
179             //handling/checking imports regarding root modules
180             for (ModuleImport moduleImport : module.getImports()) {
181
182                 if (moduleImport.getModuleName().equals(rootModule.getName())) {
183
184                     if (moduleImport.getRevision().isPresent()
185                             && !moduleImport.getRevision().equals(rootModule.getRev())) {
186                         return false;
187                     }
188
189                     return true;
190                 }
191             }
192
193             //submodules handling
194             for (Module moduleSub : module.getSubmodules()) {
195                 return checkModuleDependency(moduleSub, rootModules);
196             }
197         }
198
199         return false;
200     }
201
202     @Override
203     public String toString() {
204         return String.format("SchemaContextProxyImpl{filteredModules=%s}", filteredModules);
205     }
206
207     public static final class ModuleId {
208         private final String name;
209         private final Revision rev;
210
211         public ModuleId(final String name, final Optional<Revision> rev) {
212             Preconditions.checkArgument(!Strings.isNullOrEmpty(name),
213                     "No module dependency name given. Nothing to do.");
214             this.name = name;
215             Preconditions.checkArgument(rev.isPresent(), "No revision date given. Nothing to do.");
216             this.rev = rev.get();
217         }
218
219         public String getName() {
220             return name;
221         }
222
223         public Optional<Revision> getRev() {
224             return Optional.ofNullable(rev);
225         }
226
227         public static final Function<Module, ModuleId> MODULE_TO_MODULE_ID = input -> new ModuleId(input.getName(),
228             input.getRevision());
229
230         @Override
231         public boolean equals(final Object obj) {
232             if (this == obj) {
233                 return true;
234             }
235             if (!(obj instanceof ModuleId)) {
236                 return false;
237             }
238
239             ModuleId moduleId = (ModuleId) obj;
240             if (name != null ? !name.equals(moduleId.name) : moduleId.name != null) {
241                 return false;
242             }
243             if (rev != null ? !rev.equals(moduleId.rev) : moduleId.rev != null) {
244                 return false;
245             }
246
247             return true;
248         }
249
250         @Override
251         public int hashCode() {
252             int result = name != null ? name.hashCode() : 0;
253             result = 31 * result + (rev != null ? rev.hashCode() : 0);
254             return result;
255         }
256
257         @Override
258         public String toString() {
259             return String.format("ModuleId{name='%s', rev=%s}",name,rev);
260         }
261     }
262 }