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