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