| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| package org.apache.ivy.core.resolve; |
| |
| import java.util.ArrayDeque; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Deque; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedHashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.Stack; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import org.apache.ivy.core.IvyContext; |
| import org.apache.ivy.core.LogOptions; |
| import org.apache.ivy.core.event.resolve.EndResolveDependencyEvent; |
| import org.apache.ivy.core.event.resolve.StartResolveDependencyEvent; |
| import org.apache.ivy.core.module.descriptor.Artifact; |
| import org.apache.ivy.core.module.descriptor.Configuration; |
| import org.apache.ivy.core.module.descriptor.DefaultArtifact; |
| import org.apache.ivy.core.module.descriptor.DependencyArtifactDescriptor; |
| import org.apache.ivy.core.module.descriptor.DependencyDescriptor; |
| import org.apache.ivy.core.module.descriptor.IncludeRule; |
| import org.apache.ivy.core.module.descriptor.MDArtifact; |
| import org.apache.ivy.core.module.descriptor.ModuleDescriptor; |
| import org.apache.ivy.core.module.id.ArtifactId; |
| import org.apache.ivy.core.module.id.ModuleId; |
| import org.apache.ivy.core.module.id.ModuleRevisionId; |
| import org.apache.ivy.core.resolve.IvyNodeCallers.Caller; |
| import org.apache.ivy.core.resolve.IvyNodeEviction.EvictionData; |
| import org.apache.ivy.plugins.conflict.ConflictManager; |
| import org.apache.ivy.plugins.conflict.LatestCompatibleConflictManager; |
| import org.apache.ivy.plugins.matcher.MatcherHelper; |
| import org.apache.ivy.plugins.resolver.DependencyResolver; |
| import org.apache.ivy.util.Message; |
| import org.apache.ivy.util.StringUtils; |
| import org.apache.ivy.util.filter.Filter; |
| import org.apache.ivy.util.filter.FilterHelper; |
| |
| import static org.apache.ivy.core.module.descriptor.Configuration.Visibility.PRIVATE; |
| import static org.apache.ivy.core.module.descriptor.Configuration.Visibility.PUBLIC; |
| import static org.apache.ivy.util.StringUtils.splitToArray; |
| |
| public class IvyNode implements Comparable<IvyNode> { |
| private static final Pattern FALLBACK_CONF_PATTERN = Pattern.compile("(.+)\\((.*)\\)"); |
| |
| // //////// CONTEXT |
| private ResolveData data; |
| |
| private ResolveEngineSettings settings; |
| |
| // //////// DELEGATES |
| private IvyNodeCallers callers; |
| |
| private IvyNodeEviction eviction; |
| |
| // //////// MAIN DATA |
| |
| private IvyNode root; |
| |
| // id as requested, i.e. may be with latest rev |
| private ModuleRevisionId id; |
| |
| // set only when node has been built or updated from a DependencyDescriptor |
| private Map<IvyNode, DependencyDescriptor> dds = new HashMap<>(); |
| |
| // Set when data has been loaded only, or when constructed from a module descriptor |
| private ModuleDescriptor md; |
| |
| private ResolvedModuleRevision module; |
| |
| // //////// LOADING METADATA |
| private Exception problem = null; |
| |
| private boolean downloaded = false; |
| |
| private boolean searched = false; |
| |
| private Collection<String> confsToFetch = new HashSet<>(); |
| |
| private Collection<String> fetchedConfigurations = new HashSet<>(); |
| |
| private Collection<String> loadedRootModuleConfs = new HashSet<>(); |
| |
| // //////// USAGE DATA |
| |
| private IvyNodeUsage usage = new IvyNodeUsage(this); |
| |
| // usage information merged from evicted nodes this node is "replacing" |
| private Map<ModuleRevisionId, IvyNodeUsage> mergedUsages = new LinkedHashMap<>(); |
| |
| public IvyNode(ResolveData data, IvyNode parent, DependencyDescriptor dd) { |
| id = dd.getDependencyRevisionId(); |
| dds.put(parent, dd); |
| root = parent.getRoot(); |
| init(data); |
| } |
| |
| public IvyNode(ResolveData data, ModuleDescriptor md) { |
| id = md.getModuleRevisionId(); |
| this.md = md; |
| root = this; |
| init(data); |
| } |
| |
| private void init(ResolveData data) { |
| this.data = data; |
| settings = data.getSettings(); |
| eviction = new IvyNodeEviction(this); |
| callers = new IvyNodeCallers(this); |
| } |
| |
| /** |
| * After the call node may be discarded. To avoid using discarded node, make sure to get the |
| * real node after the call |
| * <code>IvyNode node = ... node.loadData(); node = node.getRealNode(); ...</code> |
| * |
| * @param rootModuleConf String |
| * @param parent IvyNode |
| * @param parentConf String |
| * @param conf String |
| * @param shouldBePublic boolean |
| * @param usage IvyNodeUsage |
| * @return boolean |
| */ |
| public boolean loadData(String rootModuleConf, IvyNode parent, String parentConf, String conf, |
| boolean shouldBePublic, IvyNodeUsage usage) { |
| Message.debug("loadData of " + this.toString() + " of rootConf=" + rootModuleConf); |
| if (!isRoot() && (data.getReport() != null)) { |
| data.getReport().addDependency(this); |
| } |
| |
| boolean loaded = false; |
| if (hasProblem()) { |
| Message.debug("Node has problem. Skip loading"); |
| } else if (isEvicted(rootModuleConf)) { |
| Message.debug(rootModuleConf + " is evicted. Skip loading"); |
| } else if (!hasConfigurationsToLoad() && isRootModuleConfLoaded(rootModuleConf)) { |
| Message.debug(rootModuleConf + " is loaded and no conf to load. Skip loading"); |
| } else { |
| markRootModuleConfLoaded(rootModuleConf); |
| if (md == null) { |
| DependencyResolver resolver = data.getSettings().getResolver(getId()); |
| if (resolver == null) { |
| Message.error("no resolver found for " + getModuleId() |
| + ": check your configuration"); |
| problem = new RuntimeException("no resolver found for " + getModuleId() |
| + ": check your configuration"); |
| return false; |
| } |
| try { |
| Message.debug("\tusing " + resolver + " to resolve " + getId()); |
| DependencyDescriptor dependencyDescriptor = getDependencyDescriptor(parent); |
| long start = System.currentTimeMillis(); |
| ModuleRevisionId requestedRevisionId = dependencyDescriptor |
| .getDependencyRevisionId(); |
| data.getEventManager().fireIvyEvent( |
| new StartResolveDependencyEvent(resolver, dependencyDescriptor, |
| requestedRevisionId)); |
| module = resolver.getDependency(dependencyDescriptor, data); |
| data.getEventManager().fireIvyEvent( |
| new EndResolveDependencyEvent(resolver, dependencyDescriptor, |
| requestedRevisionId, module, System.currentTimeMillis() - start)); |
| |
| if (module != null) { |
| module.getResolver() |
| .getRepositoryCacheManager() |
| .saveResolvers(module.getDescriptor(), |
| module.getResolver().getName(), |
| module.getArtifactResolver().getName()); |
| if (settings.logModuleWhenFound() |
| && LogOptions.LOG_DEFAULT.equals(getData().getOptions().getLog())) { |
| Message.info("\tfound " + module.getId() + " in " |
| + module.getResolver().getName()); |
| } else { |
| Message.verbose("\tfound " + module.getId() + " in " |
| + module.getResolver().getName()); |
| } |
| |
| // IVY-56: check if revision has actually been resolved |
| if (settings.getVersionMatcher().isDynamic(getId()) |
| && settings.getVersionMatcher().isDynamic(module.getId())) { |
| Message.error("impossible to resolve dynamic revision for " + getId() |
| + ": check your configuration and make sure revision is part of your pattern"); |
| problem = new RuntimeException("impossible to resolve dynamic revision"); |
| return false; |
| } |
| if (!getId().equals(module.getId())) { |
| IvyNode resolved = data.getNode(module.getId()); |
| if (resolved != null) { |
| // found revision has already been resolved |
| // => update it and discard this node |
| md = module.getDescriptor(); // needed for handleConfiguration |
| if (!handleConfiguration(loaded, rootModuleConf, parent, |
| parentConf, conf, shouldBePublic, usage)) { |
| return false; |
| } |
| |
| moveToRealNode(rootModuleConf, parent, parentConf, conf, |
| shouldBePublic, resolved); |
| |
| return true; |
| } |
| String log = "\t[" + module.getId().getRevision() + "] " + getId(); |
| if (!settings.getVersionMatcher().isDynamic(getId())) { |
| log += " (forced)"; |
| } |
| if (settings.logResolvedRevision() |
| && LogOptions.LOG_DEFAULT.equals(getData().getOptions() |
| .getLog())) { |
| Message.info(log); |
| } else { |
| Message.verbose(log); |
| } |
| } |
| downloaded = module.getReport().isDownloaded(); |
| searched = module.getReport().isSearched(); |
| loaded = true; |
| |
| md = module.getDescriptor(); |
| confsToFetch.remove("*"); |
| updateConfsToFetch(Arrays |
| .asList(resolveSpecialConfigurations(getRequiredConfigurations( |
| parent, parentConf)))); |
| } else { |
| Message.warn("\tmodule not found: " + getId()); |
| resolver.reportFailure(); |
| problem = new RuntimeException("not found"); |
| return false; |
| } |
| } catch (ResolveProcessException e) { |
| throw e; |
| } catch (Exception e) { |
| problem = e; |
| Message.debug("Unexpected error: " + problem.getMessage(), problem); |
| return false; |
| } |
| } else { |
| loaded = true; |
| } |
| } |
| handleConfiguration(loaded, rootModuleConf, parent, parentConf, conf, shouldBePublic, usage); |
| if (hasProblem()) { |
| Message.debug("problem : " + problem.getMessage()); |
| return false; |
| } |
| DependencyDescriptor dd = getDependencyDescriptor(parent); |
| if (dd != null) { |
| usage.addUsage(rootModuleConf, dd, parentConf); |
| } |
| return loaded; |
| } |
| |
| private void moveToRealNode(String rootModuleConf, IvyNode parent, String parentConf, |
| String conf, boolean shouldBePublic, IvyNode resolved) { |
| if (resolved.md == null) { |
| resolved.md = md; |
| } |
| if (resolved.module == null) { |
| resolved.module = module; |
| } |
| resolved.downloaded |= module.getReport().isDownloaded(); |
| resolved.searched |= module.getReport().isSearched(); |
| resolved.dds.putAll(dds); |
| resolved.updateDataFrom(this, rootModuleConf, true); |
| resolved.loadData(rootModuleConf, parent, parentConf, conf, shouldBePublic, usage); |
| resolved.usage.updateDataFrom(getAllUsages(), rootModuleConf); |
| usage = resolved.usage; |
| |
| data.replaceNode(getId(), resolved, rootModuleConf); // this actually discards the node |
| |
| if (settings.logResolvedRevision() |
| && LogOptions.LOG_DEFAULT.equals(getData().getOptions().getLog())) { |
| Message.info("\t[" + module.getId().getRevision() + "] " + getId()); |
| } else { |
| Message.verbose("\t[" + module.getId().getRevision() + "] " + getId()); |
| } |
| } |
| |
| public Collection<IvyNode> getDependencies(String rootModuleConf, String[] confs, |
| String requestedConf) { |
| if (md == null) { |
| throw new IllegalStateException( |
| "impossible to get dependencies when data has not been loaded"); |
| } |
| if (Arrays.asList(confs).contains("*")) { |
| if (isRoot()) { |
| confs = md.getConfigurationsNames(); |
| } else { |
| confs = md.getPublicConfigurationsNames(); |
| } |
| } |
| Collection<IvyNode> deps = new HashSet<>(); |
| for (String conf : confs) { |
| deps.addAll(getDependencies(rootModuleConf, conf, requestedConf)); |
| } |
| return deps; |
| } |
| |
| /** |
| * Load the dependencies of the current node |
| * <p> |
| * The resulting collection of nodes may have some configuration to load |
| * |
| * @param rootModuleConf |
| * the requested configuration of the root module |
| * @param conf |
| * the configuration to load of this node |
| * @param requestedConf |
| * the actual node conf requested, possibly extending the <code>conf</code> one. |
| * @return {@link Collection} of {@link IvyNode} |
| */ |
| public Collection<IvyNode> getDependencies(String rootModuleConf, String conf, |
| String requestedConf) { |
| if (md == null) { |
| throw new IllegalStateException( |
| "impossible to get dependencies when data has not been loaded"); |
| } |
| // it's important to respect order => LinkedHashMap |
| Map<ModuleRevisionId, IvyNode> dependencies = new LinkedHashMap<>(); |
| for (DependencyDescriptor dependencyDescriptor : md.getDependencies()) { |
| DependencyDescriptor dd = data.mediate(dependencyDescriptor); |
| String[] dependencyConfigurations = dd.getDependencyConfigurations(conf, requestedConf); |
| if (dependencyConfigurations.length == 0) { |
| // no configuration of the dependency is required for current confs : |
| // it is exactly the same as if there was no dependency at all on it |
| continue; |
| } |
| ModuleRevisionId requestedDependencyRevisionId = dd.getDependencyRevisionId(); |
| if (isDependencyModuleExcluded(dd, rootModuleConf, requestedDependencyRevisionId, conf)) { |
| // the whole module is excluded, it is considered as not being part of dependencies |
| // at all |
| Message.verbose("excluding " + dd + " in " + conf); |
| continue; |
| } |
| |
| // check if not already loaded here |
| IvyNode depNode = dependencies.get(requestedDependencyRevisionId); |
| if (depNode == null) { |
| // check if not already loaded during the resolve session |
| depNode = data.getNode(requestedDependencyRevisionId); |
| } |
| |
| if (depNode == null) { |
| depNode = new IvyNode(data, this, dd); |
| } else { |
| depNode.addDependencyDescriptor(this, dd); |
| if (depNode.hasProblem()) { |
| // dependency already tried to be resolved, but unsuccessfully |
| // nothing special to do |
| } |
| |
| } |
| String[] confsArray = depNode.resolveSpecialConfigurations(dependencyConfigurations); |
| Collection<String> confs = Arrays.asList(confsArray); |
| depNode.updateConfsToFetch(confs); |
| depNode.addRootModuleConfigurations(depNode.usage, rootModuleConf, confsArray); |
| depNode.usage.setRequiredConfs(this, conf, confs); |
| |
| depNode.addCaller(rootModuleConf, this, conf, requestedConf, dependencyConfigurations, |
| dd); |
| dependencies.put(requestedDependencyRevisionId, depNode); |
| } |
| return dependencies.values(); |
| } |
| |
| private void addDependencyDescriptor(IvyNode parent, DependencyDescriptor dd) { |
| dds.put(parent, dd); |
| } |
| |
| public DependencyDescriptor getDependencyDescriptor(IvyNode parent) { |
| return dds.get(parent); |
| } |
| |
| private boolean isDependencyModuleExcluded(DependencyDescriptor dd, String rootModuleConf, |
| ModuleRevisionId dependencyRevisionId, String conf) { |
| Artifact a = DefaultArtifact.newIvyArtifact(dependencyRevisionId, null); |
| if (isRoot()) { |
| // no callers, but maybe some exclude |
| Boolean exclude = doesExclude(md, rootModuleConf, new String[] {rootModuleConf}, dd, a, |
| new ArrayDeque<IvyNode>()); |
| return exclude != null && exclude; |
| } |
| return callers.doesCallersExclude(rootModuleConf, a); |
| } |
| |
| Boolean doesExclude(ModuleDescriptor md, String rootModuleConf, String[] moduleConfs, |
| DependencyDescriptor dd, Artifact artifact, Deque<IvyNode> callersStack) { |
| // artifact is excluded if it match any of the exclude pattern for this dependency... |
| if (directlyExcludes(md, moduleConfs, dd, artifact)) { |
| return Boolean.TRUE; |
| } |
| // ... or if it is excluded by all its callers |
| IvyNode c = getData().getNode(md.getModuleRevisionId()); |
| if (c != null) { |
| if (callersStack.contains(c)) { |
| // a circular dependency, we cannot be conclusive here |
| return null; |
| } |
| return c.doesCallersExclude(rootModuleConf, artifact, callersStack); |
| } |
| return Boolean.FALSE; |
| } |
| |
| public boolean directlyExcludes(ModuleDescriptor md, String[] moduleConfs, |
| DependencyDescriptor dd, Artifact artifact) { |
| return dd != null && dd.doesExclude(moduleConfs, artifact.getId().getArtifactId()) |
| || md.doesExclude(moduleConfs, artifact.getId().getArtifactId()); |
| } |
| |
| public boolean hasConfigurationsToLoad() { |
| return !confsToFetch.isEmpty(); |
| } |
| |
| private boolean markRootModuleConfLoaded(String rootModuleConf) { |
| return loadedRootModuleConfs.add(rootModuleConf); |
| } |
| |
| private boolean isRootModuleConfLoaded(String rootModuleConf) { |
| return loadedRootModuleConfs.contains(rootModuleConf); |
| } |
| |
| private boolean handleConfiguration(boolean loaded, String rootModuleConf, IvyNode parent, |
| String parentConf, String conf, boolean shouldBePublic, IvyNodeUsage usage) { |
| if (md != null) { |
| String[] confs = getRealConfs(conf); |
| addRootModuleConfigurations(usage, rootModuleConf, confs); |
| for (String realConf : confs) { |
| Configuration c = md.getConfiguration(realConf); |
| if (c == null) { |
| confsToFetch.remove(conf); |
| if (isConfRequiredByMergedUsageOnly(rootModuleConf, conf)) { |
| Message.verbose("configuration required by evicted revision is not available in " |
| + "selected revision. skipping " + conf + " in " + this); |
| } else if (!conf.equals(realConf)) { |
| problem = new RuntimeException("configuration not found in " + this + ": '" |
| + conf + "'. Missing configuration: '" + realConf |
| + "'. It was required from " + parent + " " + parentConf); |
| } else { |
| problem = new RuntimeException("configuration not found in " + this + ": '" |
| + realConf + "'. It was required from " + parent + " " + parentConf); |
| } |
| return false; |
| } |
| if (shouldBePublic && !isRoot() && !PUBLIC.equals(c.getVisibility())) { |
| confsToFetch.remove(conf); |
| if (isConfRequiredByMergedUsageOnly(rootModuleConf, conf)) { |
| Message.verbose("configuration required by evicted revision is not visible in " |
| + "selected revision. skipping " + conf + " in " + this); |
| } else { |
| problem = new RuntimeException("configuration not public in " + this |
| + ": '" + c + "'. It was required from " + parent + " " |
| + parentConf); |
| } |
| return false; |
| } |
| } |
| if (loaded) { |
| fetchedConfigurations.add(conf); |
| confsToFetch.removeAll(Arrays.asList(confs)); |
| confsToFetch.remove(conf); |
| } |
| } |
| return true; |
| } |
| |
| private String getDefaultConf(String conf) { |
| Matcher m = FALLBACK_CONF_PATTERN.matcher(conf); |
| return m.matches() ? m.group(2) : conf; |
| } |
| |
| private String getMainConf(String conf) { |
| Matcher m = FALLBACK_CONF_PATTERN.matcher(conf); |
| return m.matches() ? m.group(1) : null; |
| } |
| |
| public void updateConfsToFetch(Collection<String> confs) { |
| confsToFetch.addAll(confs); |
| confsToFetch.removeAll(fetchedConfigurations); |
| } |
| |
| /** |
| * resolve the '*' special configurations if necessary and possible |
| */ |
| private String[] resolveSpecialConfigurations(String[] dependencyConfigurations) { |
| if (dependencyConfigurations.length == 1 && dependencyConfigurations[0].startsWith("*") |
| && isLoaded()) { |
| String conf = dependencyConfigurations[0]; |
| if ("*".equals(conf)) { |
| return getDescriptor().getPublicConfigurationsNames(); |
| } |
| // there are exclusions in the configuration |
| List<String> exclusions = Arrays.asList(conf.substring(2).split("\\!")); |
| |
| List<String> ret = new ArrayList<>(Arrays.asList(getDescriptor() |
| .getPublicConfigurationsNames())); |
| ret.removeAll(exclusions); |
| |
| return ret.toArray(new String[ret.size()]); |
| } |
| return dependencyConfigurations; |
| } |
| |
| /** |
| * returns the required configurations from the given node |
| * |
| * @param in IvyNode |
| * @param inConf ditto |
| * @return array of configuration names |
| */ |
| public String[] getRequiredConfigurations(IvyNode in, String inConf) { |
| Collection<String> req = new LinkedHashSet<>(); |
| addAllIfNotNull(req, usage.getRequiredConfigurations(in, inConf)); |
| for (IvyNodeUsage usage : mergedUsages.values()) { |
| addAllIfNotNull(req, usage.getRequiredConfigurations(in, inConf)); |
| } |
| return req.toArray(new String[req.size()]); |
| } |
| |
| private <T> void addAllIfNotNull(Collection<T> into, Collection<T> col) { |
| if (col != null) { |
| into.addAll(col); |
| } |
| } |
| |
| /** |
| * returns all the current required configurations of the node |
| * |
| * @return array of configuration names |
| */ |
| public String[] getRequiredConfigurations() { |
| Collection<String> required = new ArrayList<>(confsToFetch.size() |
| + fetchedConfigurations.size()); |
| required.addAll(fetchedConfigurations); |
| required.addAll(confsToFetch); |
| return required.toArray(new String[required.size()]); |
| } |
| |
| public Configuration getConfiguration(String conf) { |
| if (md == null) { |
| throw new IllegalStateException( |
| "impossible to get configuration when data has not been loaded"); |
| } |
| String defaultConf = getDefaultConf(conf); |
| conf = getMainConf(conf); |
| Configuration configuration = md.getConfiguration(conf); |
| if (configuration == null) { |
| configuration = md.getConfiguration(defaultConf); |
| } |
| return configuration; |
| } |
| |
| /** |
| * Returns the configurations of the dependency required in a given root module configuration. |
| * |
| * @param rootModuleConf String |
| * @return array of configuration names |
| */ |
| public String[] getConfigurations(String rootModuleConf) { |
| Set<String> depConfs = new LinkedHashSet<>(); |
| addAllIfNotNull(depConfs, usage.getConfigurations(rootModuleConf)); |
| for (IvyNodeUsage usage : mergedUsages.values()) { |
| addAllIfNotNull(depConfs, usage.getConfigurations(rootModuleConf)); |
| } |
| return depConfs.toArray(new String[depConfs.size()]); |
| } |
| |
| protected boolean isConfRequiredByMergedUsageOnly(String rootModuleConf, String conf) { |
| Set<String> confs = usage.getConfigurations(rootModuleConf); |
| return confs == null || !confs.contains(conf); |
| } |
| |
| // This is never called. Could we remove it? |
| @Deprecated |
| public void discardConf(String rootModuleConf, String conf) { |
| Set<String> depConfs = usage.addAndGetConfigurations(rootModuleConf); |
| if (md == null) { |
| depConfs.remove(conf); |
| } else { |
| // remove all given dependency configurations to the set + extended ones |
| Configuration c = md.getConfiguration(conf); |
| if (conf == null) { |
| Message.warn("unknown configuration in " + getId() + ": " + conf); |
| } else { |
| // recursive remove of extended configurations |
| for (String ext : c.getExtends()) { |
| discardConf(rootModuleConf, ext); |
| } |
| depConfs.remove(c.getName()); |
| } |
| } |
| } |
| |
| private void addRootModuleConfigurations(IvyNodeUsage usage, String rootModuleConf, |
| String[] dependencyConfs) { |
| if (md != null) { |
| // add all given dependency configurations to the set + extended ones |
| for (String dependencyConf : dependencyConfs) { |
| Configuration conf = md.getConfiguration(dependencyConf); |
| if (conf != null) { |
| // recursive add of extended |
| addRootModuleConfigurations(usage, rootModuleConf, conf.getExtends()); |
| } |
| } |
| } |
| Collections.addAll(usage.addAndGetConfigurations(rootModuleConf), dependencyConfs); |
| } |
| |
| /** |
| * Returns the root module configurations in which this dependency is required |
| * |
| * @return array of configuration names |
| */ |
| public String[] getRootModuleConfigurations() { |
| Set<String> confs = getRootModuleConfigurationsSet(); |
| return confs.toArray(new String[confs.size()]); |
| } |
| |
| /** |
| * Returns the root module configurations in which this dependency is required |
| * |
| * @return {@link Set} of configuration names |
| */ |
| public Set<String> getRootModuleConfigurationsSet() { |
| Set<String> confs = new LinkedHashSet<>(); |
| addAllIfNotNull(confs, usage.getRootModuleConfigurations()); |
| for (IvyNodeUsage usage : mergedUsages.values()) { |
| addAllIfNotNull(confs, usage.getRootModuleConfigurations()); |
| } |
| return confs; |
| } |
| |
| public String[] getConfsToFetch() { |
| return confsToFetch.toArray(new String[confsToFetch.size()]); |
| } |
| |
| public String[] getRealConfs(String conf) { |
| if (md == null) { |
| return new String[] {conf}; |
| } |
| String defaultConf = getDefaultConf(conf); |
| conf = getMainConf(conf); |
| if (md.getConfiguration(conf) == null |
| || PRIVATE.equals(md.getConfiguration(conf).getVisibility())) { |
| if ("".equals(defaultConf)) { |
| return new String[0]; |
| } |
| conf = defaultConf; |
| } |
| if (conf.charAt(0) == '*') { |
| return resolveSpecialConfigurations(new String[] {conf}); |
| } |
| if (conf.contains(",")) { |
| return splitToArray(conf); |
| } |
| return new String[] {conf}; |
| |
| } |
| |
| /** |
| * Finds and returns a path in callers from the given module id to the current node |
| * |
| * @param from |
| * the module id to start the path from |
| * @return a collection representing the path, starting with the from node, followed by the list |
| * of nodes being one path to the current node, excluded |
| */ |
| private Collection<IvyNode> findPath(ModuleId from) { |
| return findPath(from, this, new LinkedList<IvyNode>()); |
| } |
| |
| private Collection<IvyNode> findPath(ModuleId from, IvyNode node, List<IvyNode> path) { |
| IvyNode parent = node.getDirectCallerFor(from); |
| if (parent == null) { |
| throw new IllegalArgumentException("no path from " + from + " to " + getId() + " found"); |
| } |
| if (path.contains(parent)) { |
| path.add(0, parent); |
| Message.verbose( |
| "circular dependency found while looking for the path for another one: was looking for " |
| + from + " as a caller of " + path.get(path.size() - 1)); |
| return path; |
| } |
| path.add(0, parent); |
| if (parent.getId().getModuleId().equals(from)) { |
| return path; |
| } |
| return findPath(from, parent, path); |
| } |
| |
| /** |
| * Update data in this node from data of the given node, for the given root module |
| * configuration. |
| * |
| * @param node |
| * the source node from which data should be copied |
| * @param rootModuleConf |
| * the root module configuration for which data should be updated |
| * @param real |
| * true if the node to update from actually corresponds to the same real node |
| * (usually updated because of dynamic revision resolution), false if it's not the |
| * same real node (usually updated because of node eviction) |
| */ |
| private void updateDataFrom(IvyNode node, String rootModuleConf, boolean real) { |
| // update callers |
| callers.updateFrom(node.callers, rootModuleConf, real); |
| |
| if (real) { |
| usage.updateDataFrom(node.getAllUsages(), rootModuleConf); |
| } else { |
| // let's copy usage information for the given rootModuleConf, into a separate usage |
| // object to keep detailed data about where usage comes from |
| IvyNodeUsage mergedUsage = mergedUsages.get(node.getId()); |
| if (mergedUsage == null) { |
| mergedUsage = new IvyNodeUsage(node); |
| mergedUsages.put(node.getId(), mergedUsage); |
| } |
| mergedUsage.updateDataFrom(node.getAllUsages(), rootModuleConf); |
| } |
| |
| // update confsToFetch |
| updateConfsToFetch(node.fetchedConfigurations); |
| updateConfsToFetch(node.confsToFetch); |
| } |
| |
| private Collection<IvyNodeUsage> getAllUsages() { |
| Collection<IvyNodeUsage> usages = new ArrayList<>(); |
| usages.add(usage); |
| usages.addAll(mergedUsages.values()); |
| return usages; |
| } |
| |
| /** |
| * Returns all the artifacts of this dependency required in all the root module configurations |
| * |
| * @return array of {@link Artifact}s |
| */ |
| public Artifact[] getAllArtifacts() { |
| Set<Artifact> ret = new HashSet<>(); |
| for (String rootModuleConf : getRootModuleConfigurationsSet()) { |
| ret.addAll(Arrays.asList(getArtifacts(rootModuleConf))); |
| } |
| return ret.toArray(new Artifact[ret.size()]); |
| } |
| |
| /** |
| * Returns all the artifacts of this dependency required in the root module configurations in |
| * which the node is not evicted nor blacklisted |
| * |
| * @param artifactFilter Filter |
| * @return array of {@link Artifact}s |
| */ |
| public Artifact[] getSelectedArtifacts(Filter<Artifact> artifactFilter) { |
| Collection<Artifact> ret = new HashSet<>(); |
| for (String rootModuleConf : getRootModuleConfigurationsSet()) { |
| if (!isEvicted(rootModuleConf) && !isBlacklisted(rootModuleConf)) { |
| ret.addAll(Arrays.asList(getArtifacts(rootModuleConf))); |
| } |
| } |
| ret = FilterHelper.filter(ret, artifactFilter); |
| return ret.toArray(new Artifact[ret.size()]); |
| } |
| |
| /** |
| * Returns the artifacts of this dependency required in the configurations themselves required |
| * in the given root module configuration |
| * |
| * @param rootModuleConf String |
| * @return array of {@link Artifact}s |
| */ |
| public Artifact[] getArtifacts(String rootModuleConf) { |
| // first we look for the dependency configurations required |
| // in the given root module configuration |
| String[] confs = getConfigurations(rootModuleConf); |
| if (confs == null || confs.length == 0) { |
| // no configuration required => no artifact required |
| return new Artifact[0]; |
| } |
| if (md == null) { |
| throw new IllegalStateException( |
| "impossible to get artifacts when data has not been loaded. IvyNode = " |
| + this); |
| } |
| |
| Set<Artifact> artifacts = new HashSet<>(); // the set we fill before returning |
| |
| // we check if we have dependencyArtifacts includes description for this rootModuleConf |
| Set<DependencyArtifactDescriptor> dependencyArtifacts = usage |
| .getDependencyArtifactsSet(rootModuleConf); |
| |
| if (md.isDefault() && dependencyArtifacts != null && !dependencyArtifacts.isEmpty()) { |
| addArtifactsFromOwnUsage(artifacts, dependencyArtifacts); |
| addArtifactsFromMergedUsage(rootModuleConf, artifacts); |
| } else { |
| Set<IncludeRule> includes = new LinkedHashSet<>(); |
| addAllIfNotNull(includes, usage.getDependencyIncludesSet(rootModuleConf)); |
| for (IvyNodeUsage usage : mergedUsages.values()) { |
| addAllIfNotNull(includes, usage.getDependencyIncludesSet(rootModuleConf)); |
| } |
| |
| if ((dependencyArtifacts == null || dependencyArtifacts.isEmpty()) |
| && includes.isEmpty()) { |
| // no artifacts / includes: we get all artifacts as defined by the descriptor |
| for (String conf : confs) { |
| artifacts.addAll(Arrays.asList(md.getArtifacts(conf))); |
| } |
| } else { |
| // we have to get only artifacts listed as "includes"; |
| // first we get all artifacts as defined by the module descriptor |
| // and classify them by artifact id |
| Map<ArtifactId, Artifact> allArtifacts = new HashMap<>(); |
| for (String conf : confs) { |
| for (Artifact art : md.getArtifacts(conf)) { |
| allArtifacts.put(art.getId().getArtifactId(), art); |
| } |
| } |
| |
| // now we add caller defined ones |
| if (dependencyArtifacts != null) { |
| addArtifactsFromOwnUsage(artifacts, dependencyArtifacts); |
| } |
| addArtifactsFromMergedUsage(rootModuleConf, artifacts); |
| |
| // and now we filter according to include rules |
| Iterator<IncludeRule> it = includes.iterator(); |
| while (it.hasNext()) { |
| IncludeRule dad = it.next(); |
| Collection<Artifact> arts = findArtifactsMatching(dad, allArtifacts); |
| if (arts.isEmpty()) { |
| Message.error("a required artifact is not listed by module descriptor: " |
| + dad.getId()); |
| // we remove it from required list to prevent message to be displayed more |
| // than once |
| it.remove(); |
| } else { |
| Message.debug(this + " in " + rootModuleConf + ": including " + arts); |
| artifacts.addAll(arts); |
| } |
| } |
| } |
| } |
| |
| // now exclude artifacts that aren't accepted by any caller |
| Iterator<Artifact> iter = artifacts.iterator(); |
| while (iter.hasNext()) { |
| Artifact artifact = iter.next(); |
| boolean excluded = callers.doesCallersExclude(rootModuleConf, artifact); |
| if (excluded) { |
| Message.debug(this + " in " + rootModuleConf + ": excluding " + artifact); |
| iter.remove(); |
| } |
| } |
| return artifacts.toArray(new Artifact[artifacts.size()]); |
| } |
| |
| private void addArtifactsFromOwnUsage(Set<Artifact> artifacts, |
| Set<DependencyArtifactDescriptor> dependencyArtifacts) { |
| for (DependencyArtifactDescriptor dad : dependencyArtifacts) { |
| artifacts.add(new MDArtifact(md, dad.getName(), dad.getType(), dad.getExt(), dad |
| .getUrl(), dad.getQualifiedExtraAttributes())); |
| } |
| } |
| |
| private void addArtifactsFromMergedUsage(String rootModuleConf, Set<Artifact> artifacts) { |
| for (IvyNodeUsage usage : mergedUsages.values()) { |
| Set<DependencyArtifactDescriptor> mergedDependencyArtifacts = usage |
| .getDependencyArtifactsSet(rootModuleConf); |
| if (mergedDependencyArtifacts != null) { |
| for (DependencyArtifactDescriptor dad : mergedDependencyArtifacts) { |
| Map<String, String> extraAttributes = new HashMap<>( |
| dad.getQualifiedExtraAttributes()); |
| MDArtifact artifact = new MDArtifact(md, dad.getName(), dad.getType(), |
| dad.getExt(), dad.getUrl(), extraAttributes); |
| |
| if (!artifacts.contains(artifact)) { |
| // this is later used to know that this is a merged artifact |
| extraAttributes.put("ivy:merged", dad.getDependencyDescriptor() |
| .getParentRevisionId() + " -> " + usage.getNode().getId()); |
| artifacts.add(artifact); |
| } |
| } |
| } |
| } |
| } |
| |
| private static Collection<Artifact> findArtifactsMatching(IncludeRule rule, |
| Map<ArtifactId, Artifact> allArtifacts) { |
| Collection<Artifact> ret = new ArrayList<>(); |
| for (Map.Entry<ArtifactId, Artifact> entry : allArtifacts.entrySet()) { |
| if (MatcherHelper.matches(rule.getMatcher(), rule.getId(), entry.getKey())) { |
| ret.add(entry.getValue()); |
| } |
| } |
| return ret; |
| } |
| |
| public boolean hasProblem() { |
| return problem != null; |
| } |
| |
| public Exception getProblem() { |
| return problem; |
| } |
| |
| public String getProblemMessage() { |
| return StringUtils.getErrorMessage(problem); |
| } |
| |
| public boolean isDownloaded() { |
| return downloaded; |
| } |
| |
| public boolean isSearched() { |
| return searched; |
| } |
| |
| public boolean isLoaded() { |
| return md != null; |
| } |
| |
| public boolean isFetched(String conf) { |
| return fetchedConfigurations.contains(conf); |
| } |
| |
| public IvyNode findNode(ModuleRevisionId mrid) { |
| return data.getNode(mrid); |
| } |
| |
| boolean isRoot() { |
| return root == this; |
| } |
| |
| public IvyNode getRoot() { |
| return root; |
| } |
| |
| public ConflictManager getConflictManager(ModuleId mid) { |
| if (md == null) { |
| throw new IllegalStateException( |
| "impossible to get conflict manager when data has not been loaded. IvyNode = " |
| + this); |
| } |
| ConflictManager cm = md.getConflictManager(mid); |
| return cm == null ? settings.getConflictManager(mid) : cm; |
| } |
| |
| public IvyNode getRealNode() { |
| IvyNode real = data.getNode(getId()); |
| return real != null ? real : this; |
| } |
| |
| public ModuleRevisionId getId() { |
| return id; |
| } |
| |
| public ModuleId getModuleId() { |
| return id.getModuleId(); |
| } |
| |
| public ModuleDescriptor getDescriptor() { |
| return md; |
| } |
| |
| public ResolveData getData() { |
| return data; |
| } |
| |
| public ResolvedModuleRevision getModuleRevision() { |
| return module; |
| } |
| |
| public long getPublication() { |
| if (module != null) { |
| return module.getPublicationDate().getTime(); |
| } |
| return 0; |
| } |
| |
| /** |
| * Returns the last modified timestamp of the module represented by this Node, or 0 if the last |
| * modified timestamp is currently unknown (module not loaded) |
| * |
| * @return the last modified timestamp of the module represented by this Node |
| */ |
| public long getLastModified() { |
| if (md != null) { |
| return md.getLastModified(); |
| } |
| return 0; |
| } |
| |
| public ModuleRevisionId getResolvedId() { |
| if (md != null && md.getResolvedModuleRevisionId().getRevision() != null) { |
| return md.getResolvedModuleRevisionId(); |
| } |
| if (module != null) { |
| return module.getId(); |
| } |
| return getId(); |
| } |
| |
| /** |
| * Clean data related to one root module configuration only |
| */ |
| public void clean() { |
| confsToFetch.clear(); |
| } |
| |
| // ///////////////////////////////////////////////////////////////////////////// |
| // CALLERS MANAGEMENT |
| // ///////////////////////////////////////////////////////////////////////////// |
| |
| boolean canExclude(String rootModuleConf) { |
| for (Caller caller : getCallers(rootModuleConf)) { |
| if (caller.canExclude()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private IvyNode getDirectCallerFor(ModuleId from) { |
| return callers.getDirectCallerFor(from); |
| } |
| |
| public Caller[] getCallers(String rootModuleConf) { |
| return callers.getCallers(rootModuleConf); |
| } |
| |
| public Collection<ModuleId> getAllCallersModuleIds() { |
| return callers.getAllCallersModuleIds(); |
| } |
| |
| public Caller[] getAllCallers() { |
| return callers.getAllCallers(); |
| } |
| |
| public Caller[] getAllRealCallers() { |
| return callers.getAllRealCallers(); |
| } |
| |
| public void addCaller(String rootModuleConf, IvyNode callerNode, String callerConf, |
| String requestedConf, String[] dependencyConfs, DependencyDescriptor dd) { |
| callers.addCaller(rootModuleConf, callerNode, callerConf, requestedConf, dependencyConfs, |
| dd); |
| boolean isCircular = callers.getAllCallersModuleIds().contains(getId().getModuleId()); |
| if (isCircular) { |
| IvyContext.getContext().getCircularDependencyStrategy() |
| .handleCircularDependency(toMrids(findPath(getId().getModuleId()), this)); |
| } |
| } |
| |
| public boolean doesCallersExclude(String rootModuleConf, Artifact artifact, |
| Deque<IvyNode> callersStack) { |
| return callers.doesCallersExclude(rootModuleConf, artifact, callersStack); |
| } |
| |
| @Deprecated |
| public boolean doesCallersExclude(String rootModuleConf, Artifact artifact, |
| Stack<ModuleRevisionId> callersStack) { |
| Deque<IvyNode> callersDeque = new ArrayDeque<>(); |
| for (ModuleRevisionId mrid : callersStack) { |
| for (Caller caller : getCallers(rootModuleConf)) { |
| if (caller.getModuleRevisionId().equals(mrid)) { |
| callersDeque.add(data.getNode(mrid)); |
| } |
| } |
| } |
| return callers.doesCallersExclude(rootModuleConf, artifact, callersDeque); |
| } |
| |
| private ModuleRevisionId[] toMrids(Collection<IvyNode> path, IvyNode depNode) { |
| ModuleRevisionId[] ret = new ModuleRevisionId[path.size() + 1]; |
| int i = 0; |
| for (IvyNode node : path) { |
| ret[i++] = node.getId(); |
| } |
| ret[ret.length - 1] = depNode.getId(); |
| return ret; |
| } |
| |
| // ///////////////////////////////////////////////////////////////////////////// |
| // EVICTION MANAGEMENT |
| // ///////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * A copy of the set of resolved nodes (real nodes) |
| * |
| * @param moduleId ditto |
| * @param rootModuleConf String |
| * @return Set<IvyNode> |
| */ |
| public Set<IvyNode> getResolvedNodes(ModuleId moduleId, String rootModuleConf) { |
| return eviction.getResolvedNodes(moduleId, rootModuleConf); |
| } |
| |
| public Collection<ModuleRevisionId> getResolvedRevisions(ModuleId moduleId, |
| String rootModuleConf) { |
| return eviction.getResolvedRevisions(moduleId, rootModuleConf); |
| } |
| |
| public void markEvicted(EvictionData evictionData) { |
| eviction.markEvicted(evictionData); |
| String rootModuleConf = evictionData.getRootModuleConf(); |
| |
| // bug 105: update selected data with evicted one |
| if (evictionData.getSelected() != null) { |
| for (IvyNode selected : evictionData.getSelected()) { |
| selected.updateDataFrom(this, rootModuleConf, false); |
| } |
| } |
| } |
| |
| public Collection<ConflictManager> getAllEvictingConflictManagers() { |
| return eviction.getAllEvictingConflictManagers(); |
| } |
| |
| public Collection<IvyNode> getAllEvictingNodes() { |
| return eviction.getAllEvictingNodes(); |
| } |
| |
| public Collection<String> getAllEvictingNodesDetails() { |
| return eviction.getAllEvictingNodesDetails(); |
| } |
| |
| public String[] getEvictedConfs() { |
| return eviction.getEvictedConfs(); |
| } |
| |
| public EvictionData getEvictedData(String rootModuleConf) { |
| return eviction.getEvictedData(rootModuleConf); |
| } |
| |
| public Collection<IvyNode> getEvictedNodes(ModuleId mid, String rootModuleConf) { |
| return eviction.getEvictedNodes(mid, rootModuleConf); |
| } |
| |
| public Collection<ModuleRevisionId> getEvictedRevisions(ModuleId mid, String rootModuleConf) { |
| return eviction.getEvictedRevisions(mid, rootModuleConf); |
| } |
| |
| public EvictionData getEvictionDataInRoot(String rootModuleConf, IvyNode ancestor) { |
| return eviction.getEvictionDataInRoot(rootModuleConf, ancestor); |
| } |
| |
| public boolean isCompletelyEvicted() { |
| return eviction.isCompletelyEvicted(); |
| } |
| |
| public boolean isEvicted(String rootModuleConf) { |
| return eviction.isEvicted(rootModuleConf); |
| } |
| |
| public void markEvicted(String rootModuleConf, IvyNode node, ConflictManager conflictManager, |
| Collection<IvyNode> resolved) { |
| EvictionData evictionData = new EvictionData(rootModuleConf, node, conflictManager, |
| resolved); |
| markEvicted(evictionData); |
| } |
| |
| public void setEvictedNodes(ModuleId moduleId, String rootModuleConf, |
| Collection<IvyNode> evicted) { |
| eviction.setEvictedNodes(moduleId, rootModuleConf, evicted); |
| } |
| |
| public void setResolvedNodes(ModuleId moduleId, String rootModuleConf, |
| Collection<IvyNode> resolved) { |
| eviction.setResolvedNodes(moduleId, rootModuleConf, resolved); |
| } |
| |
| @Override |
| public String toString() { |
| return getResolvedId().toString(); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (!(obj instanceof IvyNode)) { |
| return false; |
| } |
| IvyNode node = (IvyNode) obj; |
| return node.getId().equals(getId()); |
| } |
| |
| public int compareTo(IvyNode that) { |
| return this.getModuleId().compareTo(that.getModuleId()); |
| } |
| |
| @Override |
| public int hashCode() { |
| return getId().hashCode(); |
| } |
| |
| /** |
| * Returns a collection of Nodes in conflict for which conflict has been detected but conflict |
| * resolution hasn't been done yet |
| * |
| * @param rootModuleConf ditto |
| * @param mid |
| * the module id for which pending conflicts should be found |
| * @return a Collection of IvyNode in pending conflict |
| */ |
| public Collection<IvyNode> getPendingConflicts(String rootModuleConf, ModuleId mid) { |
| return eviction.getPendingConflicts(rootModuleConf, mid); |
| } |
| |
| public void setPendingConflicts(ModuleId moduleId, String rootModuleConf, |
| Collection<IvyNode> conflicts) { |
| eviction.setPendingConflicts(moduleId, rootModuleConf, conflicts); |
| } |
| |
| // ///////////////////////////////////////////////////////////////////////////// |
| // BLACKLISTING MANAGEMENT |
| // ///////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Blacklists the current node, so that a new resolve process won't ever consider this node as |
| * available in the repository. |
| * <p> |
| * This is useful in combination with {@link RestartResolveProcess} for conflict manager |
| * implementation which use a best effort strategy to find compatible dependency set, like |
| * {@link LatestCompatibleConflictManager} |
| * </p> |
| * |
| * @param bdata |
| * the root module configuration in which the node should be blacklisted |
| */ |
| public void blacklist(IvyNodeBlacklist bdata) { |
| if (data.getSettings().logResolvedRevision()) { |
| Message.info("BLACKLISTING " + bdata); |
| } else { |
| Message.verbose("BLACKLISTING " + bdata); |
| } |
| |
| Stack<IvyNode> callerStack = new Stack<>(); |
| callerStack.push(this); |
| clearEvictionDataInAllCallers(bdata.getRootModuleConf(), callerStack); |
| |
| usage.blacklist(bdata); |
| data.blacklist(this); |
| } |
| |
| private void clearEvictionDataInAllCallers(String rootModuleConf, Stack<IvyNode> callerStack) { |
| for (Caller caller : callerStack.peek().getCallers(rootModuleConf)) { |
| IvyNode callerNode = findNode(caller.getModuleRevisionId()); |
| if (callerNode != null) { |
| callerNode.eviction = new IvyNodeEviction(callerNode); |
| if (!callerStack.contains(callerNode)) { |
| callerStack.push(callerNode); |
| clearEvictionDataInAllCallers(rootModuleConf, callerStack); |
| callerStack.pop(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Indicates if this node has been blacklisted in the given root module conf. |
| * <p> |
| * A blacklisted node should be considered as if it doesn't even exist on the repository. |
| * </p> |
| * |
| * @param rootModuleConf |
| * the root module conf for which we'd like to know if the node is blacklisted |
| * |
| * @return true if this node is blacklisted int he given root module conf, false otherwise |
| * @see #blacklist(IvyNodeBlacklist) |
| */ |
| public boolean isBlacklisted(String rootModuleConf) { |
| return usage.isBlacklisted(rootModuleConf); |
| } |
| |
| /** |
| * Indicates if this node has been blacklisted in all root module configurations. |
| * |
| * @return true if this node is blacklisted in all root module configurations, false otherwise |
| * @see #blacklist(IvyNodeBlacklist) |
| */ |
| public boolean isCompletelyBlacklisted() { |
| if (isRoot()) { |
| return false; |
| } |
| for (String rootModuleConfiguration : getRootModuleConfigurations()) { |
| if (!isBlacklisted(rootModuleConfiguration)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Returns the blacklist data of this node in the given root module conf, or <code>null</code> |
| * if this node is not blacklisted in this root module conf. |
| * |
| * @param rootModuleConf |
| * the root module configuration to consider |
| * @return the blacklist data if any |
| */ |
| public IvyNodeBlacklist getBlacklistData(String rootModuleConf) { |
| return usage.getBlacklistData(rootModuleConf); |
| } |
| |
| public IvyNodeUsage getMainUsage() { |
| return usage; |
| } |
| |
| /** |
| * Indicates if there is any of the merged usages of this node which has a depender with |
| * transitive dependency descriptor. |
| * <p> |
| * If at there is at least one usage from the merged usages for which there is a depender in the |
| * given root module conf which has a dependency descriptor with transitive == true, then it |
| * returns true. Otherwise it returns false. |
| * </p> |
| * |
| * @param rootModuleConf |
| * the root module configuration to consider |
| * @return true if there is any merged usage with transitive dd, false otherwise. |
| */ |
| public boolean hasAnyMergedUsageWithTransitiveDependency(String rootModuleConf) { |
| if (mergedUsages == null) { |
| return false; |
| } |
| for (IvyNodeUsage usage : mergedUsages.values()) { |
| if (usage.hasTransitiveDepender(rootModuleConf)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| } |