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