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