001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.eclipse.aether.graph; 020 021import java.util.AbstractSet; 022import java.util.Collection; 023import java.util.Collections; 024import java.util.Iterator; 025import java.util.LinkedHashSet; 026import java.util.NoSuchElementException; 027import java.util.Objects; 028import java.util.Set; 029 030import org.eclipse.aether.artifact.Artifact; 031 032import static java.util.Objects.requireNonNull; 033 034/** 035 * A dependency to some artifact. <em>Note:</em> Instances of this class are immutable and the exposed mutators return 036 * new objects rather than changing the current instance. 037 */ 038public final class Dependency { 039 040 private final Artifact artifact; 041 042 private final String scope; 043 044 private final Boolean optional; 045 046 private final Set<Exclusion> exclusions; 047 048 /** 049 * Creates a mandatory dependency on the specified artifact with the given scope. 050 * 051 * @param artifact The artifact being depended on, must not be {@code null}. 052 * @param scope The scope of the dependency, may be {@code null}. 053 */ 054 public Dependency(Artifact artifact, String scope) { 055 this(artifact, scope, false); 056 } 057 058 /** 059 * Creates a dependency on the specified artifact with the given scope. 060 * 061 * @param artifact The artifact being depended on, must not be {@code null}. 062 * @param scope The scope of the dependency, may be {@code null}. 063 * @param optional A flag whether the dependency is optional or mandatory, may be {@code null}. 064 */ 065 public Dependency(Artifact artifact, String scope, Boolean optional) { 066 this(artifact, scope, optional, null); 067 } 068 069 /** 070 * Creates a dependency on the specified artifact with the given scope and exclusions. 071 * 072 * @param artifact The artifact being depended on, must not be {@code null}. 073 * @param scope The scope of the dependency, may be {@code null}. 074 * @param optional A flag whether the dependency is optional or mandatory, may be {@code null}. 075 * @param exclusions The exclusions that apply to transitive dependencies, may be {@code null} if none. 076 */ 077 public Dependency(Artifact artifact, String scope, Boolean optional, Collection<Exclusion> exclusions) { 078 this(artifact, scope, Exclusions.copy(exclusions), optional); 079 } 080 081 private Dependency(Artifact artifact, String scope, Set<Exclusion> exclusions, Boolean optional) { 082 // NOTE: This constructor assumes immutability of the provided exclusion collection, for internal use only 083 this.artifact = requireNonNull(artifact, "artifact cannot be null"); 084 this.scope = (scope != null) ? scope : ""; 085 this.optional = optional; 086 this.exclusions = exclusions; 087 } 088 089 /** 090 * Gets the artifact being depended on. 091 * 092 * @return The artifact, never {@code null}. 093 */ 094 public Artifact getArtifact() { 095 return artifact; 096 } 097 098 /** 099 * Sets the artifact being depended on. 100 * 101 * @param artifact The artifact, must not be {@code null}. 102 * @return The new dependency, never {@code null}. 103 */ 104 public Dependency setArtifact(Artifact artifact) { 105 if (this.artifact.equals(artifact)) { 106 return this; 107 } 108 return new Dependency(artifact, scope, exclusions, optional); 109 } 110 111 /** 112 * Gets the scope of the dependency. The scope defines in which context this dependency is relevant. 113 * 114 * @return The scope or an empty string if not set, never {@code null}. 115 */ 116 public String getScope() { 117 return scope; 118 } 119 120 /** 121 * Sets the scope of the dependency, e.g. "compile". 122 * 123 * @param scope The scope of the dependency, may be {@code null}. 124 * @return The new dependency, never {@code null}. 125 */ 126 public Dependency setScope(String scope) { 127 if (this.scope.equals(scope) || (scope == null && this.scope.isEmpty())) { 128 return this; 129 } 130 return new Dependency(artifact, scope, exclusions, optional); 131 } 132 133 /** 134 * Indicates whether this dependency is optional or not. Optional dependencies can be ignored in some contexts. 135 * 136 * @return {@code true} if the dependency is (definitively) optional, {@code false} otherwise. 137 */ 138 public boolean isOptional() { 139 return Boolean.TRUE.equals(optional); 140 } 141 142 /** 143 * Gets the optional flag for the dependency. Note: Most clients will usually call {@link #isOptional()} to 144 * determine the optional flag, this method is for advanced use cases where three-valued logic is required. 145 * 146 * @return The optional flag or {@code null} if unspecified. 147 */ 148 public Boolean getOptional() { 149 return optional; 150 } 151 152 /** 153 * Sets the optional flag for the dependency. 154 * 155 * @param optional {@code true} if the dependency is optional, {@code false} if the dependency is mandatory, may be 156 * {@code null} if unspecified. 157 * @return The new dependency, never {@code null}. 158 */ 159 public Dependency setOptional(Boolean optional) { 160 if (Objects.equals(this.optional, optional)) { 161 return this; 162 } 163 return new Dependency(artifact, scope, exclusions, optional); 164 } 165 166 /** 167 * Gets the exclusions for this dependency. Exclusions can be used to remove transitive dependencies during 168 * resolution. 169 * 170 * @return The (read-only) exclusions, never {@code null}. 171 */ 172 public Collection<Exclusion> getExclusions() { 173 return exclusions; 174 } 175 176 /** 177 * Sets the exclusions for the dependency. 178 * 179 * @param exclusions The exclusions, may be {@code null}. 180 * @return The new dependency, never {@code null}. 181 */ 182 public Dependency setExclusions(Collection<Exclusion> exclusions) { 183 if (hasEquivalentExclusions(exclusions)) { 184 return this; 185 } 186 return new Dependency(artifact, scope, optional, exclusions); 187 } 188 189 private boolean hasEquivalentExclusions(Collection<Exclusion> exclusions) { 190 if (exclusions == null || exclusions.isEmpty()) { 191 return this.exclusions.isEmpty(); 192 } 193 if (exclusions instanceof Set) { 194 return this.exclusions.equals(exclusions); 195 } 196 return exclusions.size() >= this.exclusions.size() 197 && this.exclusions.containsAll(exclusions) 198 && exclusions.containsAll(this.exclusions); 199 } 200 201 @Override 202 public String toString() { 203 return getArtifact() + " (" + getScope() + (isOptional() ? "?" : "") + ")"; 204 } 205 206 @Override 207 public boolean equals(Object obj) { 208 if (obj == this) { 209 return true; 210 } else if (obj == null || !getClass().equals(obj.getClass())) { 211 return false; 212 } 213 214 Dependency that = (Dependency) obj; 215 216 return Objects.equals(artifact, that.artifact) 217 && Objects.equals(scope, that.scope) 218 && Objects.equals(optional, that.optional) 219 && Objects.equals(exclusions, that.exclusions); 220 } 221 222 @Override 223 public int hashCode() { 224 int hash = 17; 225 hash = hash * 31 + artifact.hashCode(); 226 hash = hash * 31 + scope.hashCode(); 227 hash = hash * 31 + (optional != null ? optional.hashCode() : 0); 228 hash = hash * 31 + exclusions.size(); 229 return hash; 230 } 231 232 private static class Exclusions extends AbstractSet<Exclusion> { 233 234 private final Exclusion[] exclusions; 235 236 public static Set<Exclusion> copy(Collection<Exclusion> exclusions) { 237 if (exclusions == null || exclusions.isEmpty()) { 238 return Collections.emptySet(); 239 } 240 return new Exclusions(exclusions); 241 } 242 243 private Exclusions(Collection<Exclusion> exclusions) { 244 if (exclusions.size() > 1 && !(exclusions instanceof Set)) { 245 exclusions = new LinkedHashSet<>(exclusions); 246 } 247 this.exclusions = exclusions.toArray(new Exclusion[0]); 248 } 249 250 @Override 251 public Iterator<Exclusion> iterator() { 252 return new Iterator<Exclusion>() { 253 254 private int cursor = 0; 255 256 public boolean hasNext() { 257 return cursor < exclusions.length; 258 } 259 260 public Exclusion next() { 261 try { 262 Exclusion exclusion = exclusions[cursor]; 263 cursor++; 264 return exclusion; 265 } catch (IndexOutOfBoundsException e) { 266 throw new NoSuchElementException(); 267 } 268 } 269 270 public void remove() { 271 throw new UnsupportedOperationException(); 272 } 273 }; 274 } 275 276 @Override 277 public int size() { 278 return exclusions.length; 279 } 280 } 281}