Propagate @NonNull collection annotations
[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 package org.opendaylight.yangtools.yang.model.util;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
12
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.net.URI;
26 import java.util.ArrayList;
27 import java.util.Collection;
28 import java.util.Collections;
29 import java.util.LinkedList;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Optional;
33 import java.util.Set;
34 import java.util.TreeMap;
35 import java.util.function.Function;
36 import org.eclipse.jdt.annotation.NonNull;
37 import org.opendaylight.yangtools.yang.common.QNameModule;
38 import org.opendaylight.yangtools.yang.common.Revision;
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;
44
45 public final class FilteringSchemaContextProxy extends AbstractSchemaContext {
46     private final ImmutableMap<QNameModule, Module> moduleMap;
47
48     //collection to be filled with filtered modules
49     private final ImmutableSet<Module> filteredModules;
50
51     //collections to be filled in with filtered data
52     private final ImmutableSetMultimap<URI, Module> namespaceToModules;
53     private final ImmutableSetMultimap<String, Module> nameToModules;
54
55     /**
56      * Filters SchemaContext for yang modules.
57      *
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
62      */
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.");
67
68         final Builder<Module> filteredModulesBuilder = new Builder<>();
69
70         // preparing map to get all modules with one name but difference in revision
71         final TreeMultimap<String, Module> nameToModulesAll = TreeMultimap.create(String::compareTo,
72             REVISION_COMPARATOR);
73
74         nameToModulesAll.putAll(getStringModuleMap(delegate));
75
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);
79
80         // adding additional modules
81         processForAdditionalModules(delegate, additionalModuleIds, filteredModulesBuilder);
82
83         filteredModulesBuilder.addAll(getImportedModules(
84                 Maps.uniqueIndex(delegate.getModules(), ModuleId.MODULE_TO_MODULE_ID::apply),
85                 filteredModulesBuilder.build(), nameToModulesAll));
86
87         /**
88          * Instead of doing this on each invocation of getModules(), pre-compute it once and keep it around.
89          */
90         final List<Module> sortedModules = new ArrayList<>(filteredModulesBuilder.build());
91         sortedModules.sort(NAME_REVISION_COMPARATOR);
92         this.filteredModules = ImmutableSet.copyOf(sortedModules);
93
94         final SetMultimap<URI, 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);
103         }
104
105         namespaceToModules = ImmutableSetMultimap.copyOf(nsMap);
106         nameToModules = ImmutableSetMultimap.copyOf(nameMap);
107         moduleMap = moduleMapBuilder.build();
108     }
109
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)));
114     }
115
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)));
120     }
121
122     private static Multimap<String, ? extends Module> getStringModuleMap(final SchemaContext delegate) {
123         return Multimaps.index(delegate.getModules(), Module::getName);
124     }
125
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) {
130
131         List<Module> relatedModules = new LinkedList<>();
132
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();
138                 }
139
140                 ModuleId key = new ModuleId(moduleImport.getModuleName(), revisionDate);
141                 Module importedModule = allModules.get(key);
142
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);
147
148                 //calling imports recursive
149                 relatedModules.addAll(getImportedModules(allModules, Collections.singleton(importedModule),
150                             nameToModulesAll));
151             }
152         }
153
154         return relatedModules;
155     }
156
157     @Override
158     public Set<Module> getModules() {
159         return filteredModules;
160     }
161
162     @Override
163     protected Map<QNameModule, Module> getModuleMap() {
164         return moduleMap;
165     }
166
167     @Override
168     protected SetMultimap<URI, Module> getNamespaceToModules() {
169         return namespaceToModules;
170     }
171
172     @Override
173     protected SetMultimap<String, Module> getNameToModules() {
174         return nameToModules;
175     }
176
177     private static boolean selectAdditionalModules(final Module module, final Set<ModuleId> additionalModules) {
178         return additionalModules.contains(new ModuleId(module.getName(), module.getRevision()));
179     }
180
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()))) {
185                 return true;
186             }
187
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());
193                 }
194             }
195
196             //submodules handling
197             for (Submodule moduleSub : module.getSubmodules()) {
198                 return checkModuleDependency(moduleSub, rootModules);
199             }
200         }
201
202         return false;
203     }
204
205     @Override
206     public String toString() {
207         return String.format("SchemaContextProxyImpl{filteredModules=%s}", filteredModules);
208     }
209
210     public static final class ModuleId {
211         private final String name;
212         private final Revision rev;
213
214         public ModuleId(final String name, final Optional<Revision> rev) {
215             checkArgument(!Strings.isNullOrEmpty(name), "No module dependency name given. Nothing to do.");
216             this.name = name;
217             checkArgument(rev.isPresent(), "No revision date given. Nothing to do.");
218             this.rev = rev.get();
219         }
220
221         public String getName() {
222             return name;
223         }
224
225         public Optional<Revision> getRev() {
226             return Optional.ofNullable(rev);
227         }
228
229         public static final Function<Module, ModuleId> MODULE_TO_MODULE_ID = input -> new ModuleId(input.getName(),
230             input.getRevision());
231
232         @Override
233         public boolean equals(final Object obj) {
234             if (this == obj) {
235                 return true;
236             }
237             if (!(obj instanceof ModuleId)) {
238                 return false;
239             }
240
241             ModuleId moduleId = (ModuleId) obj;
242             if (name != null ? !name.equals(moduleId.name) : moduleId.name != null) {
243                 return false;
244             }
245             if (rev != null ? !rev.equals(moduleId.rev) : moduleId.rev != null) {
246                 return false;
247             }
248
249             return true;
250         }
251
252         @Override
253         public int hashCode() {
254             int result = name != null ? name.hashCode() : 0;
255             result = 31 * result + (rev != null ? rev.hashCode() : 0);
256             return result;
257         }
258
259         @Override
260         public String toString() {
261             return String.format("ModuleId{name='%s', rev=%s}",name,rev);
262         }
263     }
264 }