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