/* * 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 * * http://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.module.descriptor; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; 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.plugins.matcher.MatcherHelper; import org.apache.ivy.plugins.namespace.NameSpaceHelper; import org.apache.ivy.plugins.namespace.Namespace; import org.apache.ivy.plugins.namespace.NamespaceTransformer; import org.apache.ivy.util.Checks; /** * This class can be used as the default implementation for DependencyDescriptor. It implements * required methods and enables to fill dependency information with the addDependencyConfiguration * method. */ public class DefaultDependencyDescriptor implements DependencyDescriptor { private static final Pattern SELF_FALLBACK_PATTERN = Pattern .compile("@(\\+[^\\(]+)?(\\(.*\\))?"); private static final Pattern THIS_FALLBACK_PATTERN = Pattern .compile("#(\\+[^\\(]+)?(\\(.*\\))?"); /** * Transforms the given dependency descriptor of the given namespace and return a new dependency * descriptor in the system namespace. <i>Note that exclude rules are not converted in system * namespace, because they aren't transformable (the name space hasn't the ability to convert * regular expressions). However, method doesExclude will work with system artifacts.</i> * * @param dd * @param ns * @return */ public static DependencyDescriptor transformInstance(DependencyDescriptor dd, Namespace ns) { NamespaceTransformer t = ns.getToSystemTransformer(); if (t.isIdentity()) { return dd; } DefaultDependencyDescriptor newdd = transformInstance(dd, t, false); newdd.namespace = ns; return newdd; } /** * Transforms a dependency descriptor using the given transformer. Note that no namespace info * will be attached to the transformed dependency descriptor, so calling doesExclude is not * recommended (doesExclude only works when namespace is properly set) * * @param dd * @param t * @return */ public static DefaultDependencyDescriptor transformInstance(DependencyDescriptor dd, NamespaceTransformer t, boolean fromSystem) { ModuleRevisionId transformParentId = t.transform(dd.getParentRevisionId()); ModuleRevisionId transformMrid = t.transform(dd.getDependencyRevisionId()); ModuleRevisionId transformDynamicMrid = t.transform(dd .getDynamicConstraintDependencyRevisionId()); DefaultDependencyDescriptor newdd = new DefaultDependencyDescriptor(null, transformMrid, transformDynamicMrid, dd.isForce(), dd.isChanging(), dd.isTransitive()); newdd.parentId = transformParentId; ModuleRevisionId sourceModule = dd.getSourceModule(); if (sourceModule != null) { newdd.sourceModule = t.transform(sourceModule); } String[] moduleConfs = dd.getModuleConfigurations(); if (moduleConfs.length == 1 && "*".equals(moduleConfs[0])) { if (dd instanceof DefaultDependencyDescriptor) { DefaultDependencyDescriptor ddd = (DefaultDependencyDescriptor) dd; newdd.confs = new LinkedHashMap(ddd.confs); newdd.setExcludeRules(new LinkedHashMap(ddd.getExcludeRules())); newdd.setIncludeRules(new LinkedHashMap(ddd.getIncludeRules())); newdd.setDependencyArtifacts(new LinkedHashMap(ddd.getDependencyArtifacts())); } else { throw new IllegalArgumentException( "dependency descriptor transformation does not support * module confs " + "with descriptors which aren't DefaultDependencyDescriptor"); } } else { for (int i = 0; i < moduleConfs.length; i++) { newdd.confs.put(moduleConfs[i], new ArrayList(Arrays.asList(dd.getDependencyConfigurations(moduleConfs[i])))); newdd.getExcludeRules().put(moduleConfs[i], new ArrayList(Arrays.asList(dd.getExcludeRules(moduleConfs[i])))); newdd.getIncludeRules().put(moduleConfs[i], new ArrayList(Arrays.asList(dd.getIncludeRules(moduleConfs[i])))); newdd.getDependencyArtifacts().put(moduleConfs[i], new ArrayList(Arrays.asList(dd.getDependencyArtifacts(moduleConfs[i])))); } } if (fromSystem) { newdd.asSystem = dd; } return newdd; } private final ModuleRevisionId revId; private ModuleRevisionId dynamicRevId; private Map/* <String,List<String>> */confs = new LinkedHashMap(); // Map (String masterConf -> Collection(DependencyArtifactDescriptor)) private Map dependencyArtifacts; // initialized on demand only for memory consumption reason // Map (String masterConf -> Collection(IncludeRule)) private Map includeRules; // initialized on demand only for memory consumption reason // Map (String masterConf -> Collection(ExcludeRule)) private Map excludeRules; // initialized on demand only for memory consumption reason /** * Used to indicate that this revision must be used in case of conflicts, independently of * conflicts manager */ private boolean isForce; /** * Used to indicate that the dependency is a changing one, i.e. that ivy should not rely on the * version to know if it can trust artifacts in cache */ private boolean isChanging; private ModuleRevisionId parentId; private boolean isTransitive = true; /** * This namespace should be used to check */ private Namespace namespace = null; private final ModuleDescriptor md; private DependencyDescriptor asSystem = this; private ModuleRevisionId sourceModule; private DefaultDependencyDescriptor(DefaultDependencyDescriptor dd, ModuleRevisionId revision) { Checks.checkNotNull(dd, "dd"); Checks.checkNotNull(revision, "revision"); if (!revision.getModuleId().equals(dd.getDependencyId())) { throw new IllegalArgumentException( "new ModuleRevisionId MUST have the same ModuleId as original one." + " original = " + dd.getDependencyId() + " new = " + revision.getModuleId()); } md = dd.md; parentId = dd.parentId; revId = revision; dynamicRevId = dd.dynamicRevId; isForce = dd.isForce; isChanging = dd.isChanging; isTransitive = dd.isTransitive; namespace = dd.namespace; confs.putAll(dd.confs); excludeRules = dd.excludeRules == null ? null : new LinkedHashMap(dd.excludeRules); includeRules = dd.includeRules == null ? null : new LinkedHashMap(dd.includeRules); dependencyArtifacts = dd.dependencyArtifacts == null ? null : new LinkedHashMap( dd.dependencyArtifacts); sourceModule = dd.sourceModule; } public DefaultDependencyDescriptor(ModuleDescriptor md, ModuleRevisionId mrid, boolean force, boolean changing, boolean transitive) { this(md, mrid, mrid, force, changing, transitive); } public DefaultDependencyDescriptor(ModuleRevisionId mrid, boolean force) { this(mrid, force, false); } public DefaultDependencyDescriptor(ModuleRevisionId mrid, boolean force, boolean changing) { this(null, mrid, mrid, force, changing, true); } public DefaultDependencyDescriptor(ModuleDescriptor md, ModuleRevisionId mrid, ModuleRevisionId dynamicConstraint, boolean force, boolean changing, boolean transitive) { Checks.checkNotNull(mrid, "mrid"); Checks.checkNotNull(dynamicConstraint, "dynamicConstraint"); this.md = md; revId = mrid; dynamicRevId = dynamicConstraint; isForce = force; isChanging = changing; isTransitive = transitive; sourceModule = md == null ? null : md.getModuleRevisionId(); } public ModuleId getDependencyId() { return getDependencyRevisionId().getModuleId(); } public ModuleRevisionId getDependencyRevisionId() { return revId; } public ModuleRevisionId getDynamicConstraintDependencyRevisionId() { return dynamicRevId; } public String[] getModuleConfigurations() { return (String[]) confs.keySet().toArray(new String[confs.keySet().size()]); } public String[] getDependencyConfigurations(String moduleConfiguration) { return getDependencyConfigurations(moduleConfiguration, moduleConfiguration); } /** * Return the dependency configurations mapped to the given moduleConfiguration, actually * resolved because of the given requestedConfiguration * <p> * Usually requestedConfiguration and moduleConfiguration are the same, except when a conf * extends another, then the moduleConfiguration is the configuration currently resolved (the * extended one), and requestedConfiguration is the one actually requested initially (the * extending one). Both moduleConfiguration and requestedConfiguration are configurations of the * caller, the array returned is composed of the required configurations of the dependency * described by this descriptor. */ public String[] getDependencyConfigurations(String moduleConfiguration, String requestedConfiguration) { if (md != null) { Configuration c = md.getConfiguration(moduleConfiguration); if (c instanceof ConfigurationIntersection) { ConfigurationIntersection intersection = (ConfigurationIntersection) c; Set /* <String> */intersectedDepConfs = new HashSet(); String[] intersected = intersection.getIntersectedConfigurationNames(); for (int i = 0; i < intersected.length; i++) { Collection depConfs = getDependencyConfigurationsIncludingExtending( intersected[i], requestedConfiguration); if (intersectedDepConfs.isEmpty()) { intersectedDepConfs.addAll(depConfs); } else { if (intersectedDepConfs.contains("*")) { intersectedDepConfs.remove("*"); intersectedDepConfs.addAll(depConfs); } else if (depConfs.contains("*")) { // nothing to do, intersection of 'something' // with 'everything' is 'something' } else { Set /* <String> */intersectedDepConfsCopy = intersectedDepConfs; intersectedDepConfs = new HashSet(); for (Iterator it = intersectedDepConfsCopy.iterator(); it.hasNext();) { String intersectedDepConf = (String) it.next(); if (depConfs.contains(intersectedDepConf)) { // the conf is present in both sets, // so it is in the intersection intersectedDepConfs.add(intersectedDepConf); continue; } /* * we do not handle special confs like *!sg or [cond]* in right hand * confs yet: it would require supporting parenthesis grouping in * configurations intersection interpretation * * for (Iterator it2 = depConfs.iterator(); it2.hasNext();) { String * depConf = (String) it2.next(); if (depConf.startsWith("*")) { if * (intersectedDepConf .indexOf("(" + depConf + ")") != -1) { * intersectedDepConfs.add(intersectedDepConf); } else { * intersectedDepConfs.add( "(" + intersectedDepConf + ")+(" + * depConf + ")"); } } else if (intersectedDepConf.startsWith("*")) * { if (depConf .indexOf("(" + intersectedDepConf + ")") != -1) { * intersectedDepConfs.add(depConf); } else { * intersectedDepConfs.add( depConf + "+" + intersectedDepConf); } } * } */ } } } } List confsList = (List) confs.get(moduleConfiguration); if (confsList != null) { intersectedDepConfs.addAll(confsList); } if (intersectedDepConfs.isEmpty()) { List defConfs = (List) confs.get("*"); if (defConfs != null) { for (Iterator it = defConfs.iterator(); it.hasNext();) { String mappedConf = (String) it.next(); if (mappedConf != null && mappedConf.startsWith("@+")) { return new String[] {moduleConfiguration + mappedConf.substring(1)}; } else if (mappedConf != null && mappedConf.equals("@")) { return new String[] {moduleConfiguration}; } } } } return (String[]) intersectedDepConfs .toArray(new String[intersectedDepConfs.size()]); } else if (c instanceof ConfigurationGroup) { ConfigurationGroup group = (ConfigurationGroup) c; Set /* <String> */groupDepConfs = new HashSet(); String[] members = group.getMembersConfigurationNames(); for (int i = 0; i < members.length; i++) { Collection depConfs = getDependencyConfigurationsIncludingExtending(members[i], requestedConfiguration); groupDepConfs.addAll(depConfs); } return (String[]) groupDepConfs.toArray(new String[groupDepConfs.size()]); } } List confsList = (List) confs.get(moduleConfiguration); if (confsList == null) { // there is no mapping defined for this configuration, add the 'other' mappings. confsList = (List) confs.get("%"); } List defConfs = (List) confs.get("*"); Collection ret = new LinkedHashSet(); if (confsList != null) { ret.addAll(confsList); } if (defConfs != null) { ret.addAll(defConfs); } Collection replacedRet = new LinkedHashSet(); for (Iterator iter = ret.iterator(); iter.hasNext();) { String c = (String) iter.next(); String replacedConf = replaceSelfFallbackPattern(c, moduleConfiguration); if (replacedConf == null) { replacedConf = replaceThisFallbackPattern(c, requestedConfiguration); } if (replacedConf != null) { c = replacedConf; } replacedRet.add(c); } ret = replacedRet; if (ret.remove("*")) { StringBuffer r = new StringBuffer("*"); // merge excluded configurations as one conf like *!A!B for (Iterator iter = ret.iterator(); iter.hasNext();) { String c = (String) iter.next(); if (c.startsWith("!")) { r.append(c); } } return new String[] {r.toString()}; } return (String[]) ret.toArray(new String[ret.size()]); } private Collection getDependencyConfigurationsIncludingExtending(String conf, String requestedConfiguration) { Set/* <String> */allDepConfs = new LinkedHashSet(); allDepConfs .addAll(Arrays.asList(getDependencyConfigurations(conf, requestedConfiguration))); Collection extendingConfs = Configuration.findConfigurationExtending(conf, md.getConfigurations()); for (Iterator it = extendingConfs.iterator(); it.hasNext();) { Configuration extendingConf = (Configuration) it.next(); allDepConfs.addAll(Arrays.asList(getDependencyConfigurations(extendingConf.getName(), requestedConfiguration))); } return allDepConfs; } protected static String replaceSelfFallbackPattern(final String conf, final String moduleConfiguration) { return replaceFallbackConfigurationPattern(SELF_FALLBACK_PATTERN, conf, moduleConfiguration); } protected static String replaceThisFallbackPattern(final String conf, final String requestedConfiguration) { return replaceFallbackConfigurationPattern(THIS_FALLBACK_PATTERN, conf, requestedConfiguration); } /** * Replaces fallback patterns with correct values if fallback pattern exists. * * @param pattern * pattern to look for * @param conf * configuration mapping from dependency element * @param moduleConfiguration * module's configuration to use for replacement * @return Replaced string if pattern matched. Otherwise null. */ protected static String replaceFallbackConfigurationPattern(final Pattern pattern, final String conf, final String moduleConfiguration) { Matcher matcher = pattern.matcher(conf); if (matcher.matches()) { String mappedConf = moduleConfiguration; if (matcher.group(1) != null) { mappedConf = mappedConf + matcher.group(1); } if (matcher.group(2) != null) { mappedConf = mappedConf + matcher.group(2); } return mappedConf; } return null; } public String[] getDependencyConfigurations(String[] moduleConfigurations) { Set confs = new LinkedHashSet(); for (int i = 0; i < moduleConfigurations.length; i++) { confs.addAll(Arrays.asList(getDependencyConfigurations(moduleConfigurations[i]))); } if (confs.contains("*")) { return new String[] {"*"}; } return (String[]) confs.toArray(new String[confs.size()]); } public DependencyArtifactDescriptor[] getDependencyArtifacts(String moduleConfiguration) { Collection artifacts = getCollectionForConfiguration(moduleConfiguration, dependencyArtifacts); return (DependencyArtifactDescriptor[]) artifacts .toArray(new DependencyArtifactDescriptor[artifacts.size()]); } public IncludeRule[] getIncludeRules(String moduleConfiguration) { Collection rules = getCollectionForConfiguration(moduleConfiguration, includeRules); return (IncludeRule[]) rules.toArray(new IncludeRule[rules.size()]); } public ExcludeRule[] getExcludeRules(String moduleConfiguration) { Collection rules = getCollectionForConfiguration(moduleConfiguration, excludeRules); return (ExcludeRule[]) rules.toArray(new ExcludeRule[rules.size()]); } private Set getCollectionForConfiguration(String moduleConfiguration, Map collectionMap) { if (collectionMap == null || collectionMap.isEmpty()) { return Collections.EMPTY_SET; } Collection artifacts = (Collection) collectionMap.get(moduleConfiguration); Collection defArtifacts = (Collection) collectionMap.get("*"); Set ret = new LinkedHashSet(); if (artifacts != null) { ret.addAll(artifacts); } if (defArtifacts != null) { ret.addAll(defArtifacts); } return ret; } public DependencyArtifactDescriptor[] getDependencyArtifacts(String[] moduleConfigurations) { Set artifacts = new LinkedHashSet(); for (int i = 0; i < moduleConfigurations.length; i++) { artifacts.addAll(Arrays.asList(getDependencyArtifacts(moduleConfigurations[i]))); } return (DependencyArtifactDescriptor[]) artifacts .toArray(new DependencyArtifactDescriptor[artifacts.size()]); } public IncludeRule[] getIncludeRules(String[] moduleConfigurations) { Set rules = new LinkedHashSet(); for (int i = 0; i < moduleConfigurations.length; i++) { rules.addAll(Arrays.asList(getIncludeRules(moduleConfigurations[i]))); } return (IncludeRule[]) rules.toArray(new IncludeRule[rules.size()]); } public ExcludeRule[] getExcludeRules(String[] moduleConfigurations) { Set rules = new LinkedHashSet(); for (int i = 0; i < moduleConfigurations.length; i++) { rules.addAll(Arrays.asList(getExcludeRules(moduleConfigurations[i]))); } return (ExcludeRule[]) rules.toArray(new ExcludeRule[rules.size()]); } public DependencyArtifactDescriptor[] getAllDependencyArtifacts() { if (dependencyArtifacts == null) { return new DependencyArtifactDescriptor[0]; } Set ret = mergeAll(dependencyArtifacts); return (DependencyArtifactDescriptor[]) ret.toArray(new DependencyArtifactDescriptor[ret .size()]); } public IncludeRule[] getAllIncludeRules() { if (includeRules == null) { return new IncludeRule[0]; } Set ret = mergeAll(includeRules); return (IncludeRule[]) ret.toArray(new IncludeRule[ret.size()]); } public ExcludeRule[] getAllExcludeRules() { if (excludeRules == null) { return new ExcludeRule[0]; } Set ret = mergeAll(excludeRules); return (ExcludeRule[]) ret.toArray(new ExcludeRule[ret.size()]); } private Set mergeAll(Map artifactsMap) { Set ret = new LinkedHashSet(); for (Iterator it = artifactsMap.values().iterator(); it.hasNext();) { Collection artifacts = (Collection) it.next(); ret.addAll(artifacts); } return ret; } public void addDependencyConfiguration(String masterConf, String depConf) { if ((md != null) && !"*".equals(masterConf) && !"%".equals(masterConf)) { Configuration config; if (masterConf.startsWith("!")) { config = md.getConfiguration(masterConf.substring(1)); } else { config = md.getConfiguration(masterConf); } if (config == null) { throw new IllegalArgumentException("Cannot add dependency '" + revId + "' to configuration '" + masterConf + "' of module " + md.getModuleRevisionId() + " because this configuration doesn't exist!"); } if (config instanceof ConfigurationGroup) { ConfigurationGroup group = (ConfigurationGroup) config; String[] members = group.getMembersConfigurationNames(); for (int i = 0; i < members.length; i++) { addDependencyConfiguration(members[i], depConf); } return; } } List confsList = (List) confs.get(masterConf); if (confsList == null) { confsList = new ArrayList(); confs.put(masterConf, confsList); } if (!confsList.contains(depConf)) { confsList.add(depConf); } } public void addDependencyArtifact(String masterConf, DependencyArtifactDescriptor dad) { addObjectToConfiguration(masterConf, dad, getDependencyArtifacts()); } public void addIncludeRule(String masterConf, IncludeRule rule) { addObjectToConfiguration(masterConf, rule, getIncludeRules()); } public void addExcludeRule(String masterConf, ExcludeRule rule) { addObjectToConfiguration(masterConf, rule, getExcludeRules()); } private void addObjectToConfiguration(String callerConf, Object toAdd, Map confsMap) { Collection col = (Collection) confsMap.get(callerConf); if (col == null) { col = new ArrayList(); confsMap.put(callerConf, col); } col.add(toAdd); } /** * only works when namespace is properly set. The behaviour is not specified if namespace is not * set */ public boolean doesExclude(String[] moduleConfigurations, ArtifactId artifactId) { if (namespace != null) { artifactId = NameSpaceHelper .transform(artifactId, namespace.getFromSystemTransformer()); } ExcludeRule[] rules = getExcludeRules(moduleConfigurations); for (int i = 0; i < rules.length; i++) { if (MatcherHelper.matches(rules[i].getMatcher(), rules[i].getId(), artifactId)) { return true; } } return false; } /** * Returns true if this descriptor contains any exclusion rule * * @return */ public boolean canExclude() { return excludeRules != null && !excludeRules.isEmpty(); } public String toString() { return "dependency: " + revId + " " + confs; } public boolean isForce() { return isForce; } public ModuleRevisionId getParentRevisionId() { return md != null ? md.getResolvedModuleRevisionId() : parentId; } public boolean isChanging() { return isChanging; } public boolean isTransitive() { return isTransitive; } public Namespace getNamespace() { return namespace; } public String getAttribute(String attName) { return revId.getAttribute(attName); } public Map getAttributes() { return revId.getAttributes(); } public String getExtraAttribute(String attName) { return revId.getExtraAttribute(attName); } public Map getExtraAttributes() { return revId.getExtraAttributes(); } public Map getQualifiedExtraAttributes() { return revId.getQualifiedExtraAttributes(); } public DependencyDescriptor asSystem() { return asSystem; } private void setDependencyArtifacts(Map dependencyArtifacts) { this.dependencyArtifacts = dependencyArtifacts; } private Map getDependencyArtifacts() { if (dependencyArtifacts == null) { dependencyArtifacts = new LinkedHashMap(); } return dependencyArtifacts; } private void setIncludeRules(Map includeRules) { this.includeRules = includeRules; } private Map getIncludeRules() { if (includeRules == null) { includeRules = new LinkedHashMap(); } return includeRules; } private void setExcludeRules(Map excludeRules) { this.excludeRules = excludeRules; } private Map getExcludeRules() { if (excludeRules == null) { excludeRules = new LinkedHashMap(); } return excludeRules; } public ModuleRevisionId getSourceModule() { return sourceModule; } public DependencyDescriptor clone(ModuleRevisionId revision) { return new DefaultDependencyDescriptor(this, revision); } }