/* * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.yangtools.yang.model.util; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet.Builder; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import com.google.common.collect.SetMultimap; import com.google.common.collect.TreeMultimap; import java.net.URI; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.function.Function; import javax.annotation.concurrent.Immutable; import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.ModuleIdentifier; import org.opendaylight.yangtools.yang.model.api.ModuleImport; import org.opendaylight.yangtools.yang.model.api.SchemaContext; @Immutable public final class FilteringSchemaContextProxy extends AbstractSchemaContext { //collection to be filled with filtered modules private final Set<Module> filteredModules; //collections to be filled in with filtered data private final Map<ModuleIdentifier, String> identifiersToSources; private final SetMultimap<URI, Module> namespaceToModules; private final SetMultimap<String, Module> nameToModules; /** * Filters SchemaContext for yang modules * * @param delegate original SchemaContext * @param rootModules modules (yang schemas) to be available and all their dependencies (modules importing rootModule and whole chain of their imports) * @param additionalModuleIds (additional) modules (yang schemas) to be available and whole chain of their imports * */ public FilteringSchemaContextProxy(final SchemaContext delegate, final Collection<ModuleId> rootModules, final Set<ModuleId> additionalModuleIds) { Preconditions.checkArgument(rootModules!=null,"Base modules cannot be null."); Preconditions.checkArgument(additionalModuleIds!=null,"Additional modules cannot be null."); final Builder<Module> filteredModulesBuilder = new Builder<>(); final SetMultimap<URI, Module> nsMap = Multimaps.newSetMultimap(new TreeMap<>(), MODULE_SET_SUPPLIER); final SetMultimap<String, Module> nameMap = Multimaps.newSetMultimap(new TreeMap<>(), MODULE_SET_SUPPLIER); ImmutableMap.Builder<ModuleIdentifier, String> identifiersToSourcesBuilder = ImmutableMap.builder(); //preparing map to get all modules with one name but difference in revision final TreeMultimap<String, Module> nameToModulesAll = getStringModuleTreeMultimap(); nameToModulesAll.putAll(getStringModuleMap(delegate)); //in case there is a particular dependancy to view filteredModules/yang models //dependancy is checked for module name and imports processForRootModules(delegate, rootModules, filteredModulesBuilder); //adding additional modules processForAdditionalModules(delegate, additionalModuleIds, filteredModulesBuilder); filteredModulesBuilder.addAll(getImportedModules( Maps.uniqueIndex(delegate.getModules(), ModuleId.MODULE_TO_MODULE_ID::apply), filteredModulesBuilder.build(), nameToModulesAll)); /** * Instead of doing this on each invocation of getModules(), pre-compute * it once and keep it around -- better than the set we got in. */ this.filteredModules = filteredModulesBuilder.build(); for (final Module module :filteredModules) { nameMap.put(module.getName(), module); nsMap.put(module.getNamespace(), module); identifiersToSourcesBuilder.put(module, module.getSource()); } namespaceToModules = ImmutableSetMultimap.copyOf(nsMap); nameToModules = ImmutableSetMultimap.copyOf(nameMap); identifiersToSources = identifiersToSourcesBuilder.build(); } private static TreeMultimap<String, Module> getStringModuleTreeMultimap() { return TreeMultimap.create(String::compareTo, REVISION_COMPARATOR); } private static void processForAdditionalModules(final SchemaContext delegate, final Set<ModuleId> additionalModuleIds, final Builder<Module> filteredModulesBuilder) { filteredModulesBuilder.addAll(Collections2.filter(delegate.getModules(), module -> selectAdditionalModules(module, additionalModuleIds))); } private void processForRootModules(final SchemaContext delegate, final Collection<ModuleId> rootModules, final Builder<Module> filteredModulesBuilder) { filteredModulesBuilder.addAll(Collections2.filter(delegate.getModules(), module -> checkModuleDependency(module, rootModules))); } private static Multimap<String, Module> getStringModuleMap(final SchemaContext delegate) { return Multimaps.index(delegate.getModules(), Module::getName); } //dealing with imported module other than root and directly importing root private static Collection<Module> getImportedModules(final Map<ModuleId, Module> allModules, final Set<Module> baseModules, final TreeMultimap<String, Module> nameToModulesAll) { List<Module> relatedModules = Lists.newLinkedList(); for (Module module : baseModules) { for (ModuleImport moduleImport : module.getImports()) { Date revisionDate = moduleImport.getRevision() == null ? nameToModulesAll.get(moduleImport.getModuleName()).first().getRevision() : moduleImport.getRevision(); ModuleId key = new ModuleId(moduleImport.getModuleName(),revisionDate); Module importedModule = allModules.get(key); Preconditions.checkArgument(importedModule != null, "Invalid schema, cannot find imported module: %s from module: %s, %s, modules:%s", key, module.getQNameModule(), module.getName() ); relatedModules.add(importedModule); //calling imports recursive relatedModules.addAll(getImportedModules(allModules, Collections.singleton(importedModule), nameToModulesAll)); } } return relatedModules; } @Override protected Map<ModuleIdentifier, String> getIdentifiersToSources() { return identifiersToSources; } @Override public Set<Module> getModules() { return filteredModules; } @Override protected SetMultimap<URI, Module> getNamespaceToModules() { return namespaceToModules; } @Override protected SetMultimap<String, Module> getNameToModules() { return nameToModules; } private static boolean selectAdditionalModules(final Module module, final Set<ModuleId> additionalModules){ return additionalModules.contains(new ModuleId(module.getName(), module.getRevision())); } //check for any dependency regarding given string private boolean checkModuleDependency(final Module module, final Collection<ModuleId> rootModules) { for (ModuleId rootModule : rootModules) { if (rootModule.equals(new ModuleId(module.getName(), module.getRevision()))) { return true; } //handling/checking imports regarding root modules for (ModuleImport moduleImport : module.getImports()) { if (moduleImport.getModuleName().equals(rootModule.getName())) { if (moduleImport.getRevision() != null && !moduleImport.getRevision().equals(rootModule.getRev())) { return false; } return true; } } //submodules handling for (Module moduleSub : module.getSubmodules()) { return checkModuleDependency(moduleSub, rootModules); } } return false; } @Override public String toString() { return String.format("SchemaContextProxyImpl{filteredModules=%s}", filteredModules); } public static final class ModuleId { private final String name; private final Date rev; public ModuleId(final String name, final Date rev) { Preconditions.checkArgument(!Strings.isNullOrEmpty(name), "No module dependency name given. Nothing to do."); this.name = name; this.rev = Preconditions.checkNotNull(rev, "No revision date given. Nothing to do."); } public String getName() { return name; } public Date getRev() { return rev; } public static final Function<Module, ModuleId> MODULE_TO_MODULE_ID = input -> new ModuleId(input.getName(), input.getRevision()); @Override public boolean equals(final Object o) { if (this == o) { return true; } if (!(o instanceof ModuleId)) { return false; } ModuleId moduleId = (ModuleId) o; if (name != null ? !name.equals(moduleId.name) : moduleId.name != null) { return false; } if (rev != null ? !rev.equals(moduleId.rev) : moduleId.rev != null) { return false; } return true; } @Override public int hashCode() { int result = name != null ? name.hashCode() : 0; result = 31 * result + (rev != null ? rev.hashCode() : 0); return result; } @Override public String toString() { return String.format("ModuleId{name='%s', rev=%s}",name,rev); } } }