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