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.util.graph.manager; 020 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.HashMap; 024import java.util.LinkedHashSet; 025import java.util.Objects; 026 027import org.eclipse.aether.artifact.Artifact; 028import org.eclipse.aether.collection.DependencyCollectionContext; 029import org.eclipse.aether.collection.DependencyManagement; 030import org.eclipse.aether.collection.DependencyManager; 031import org.eclipse.aether.graph.Dependency; 032import org.eclipse.aether.graph.Exclusion; 033import org.eclipse.aether.scope.ScopeManager; 034import org.eclipse.aether.scope.SystemDependencyScope; 035 036import static java.util.Objects.requireNonNull; 037 038/** 039 * A dependency manager support class. 040 * <p> 041 * This implementation is Maven specific, as it works hand-in-hand along with Maven ModelBuilder. While model builder 042 * handles dependency management in the context of single POM (inheritance, imports, etc.), this implementation carries 043 * in-lineage modifications based on previously recorded dependency management rules sourced from ascendants while 044 * building the dependency graph. Root sourced management rules are special, in a way they are always applied, while 045 * en-route collected ones are carefully applied to proper descendants only, to not override work done by model 046 * builder already. 047 * <p> 048 * Details: Model builder handles version, scope from own dependency management (think "effective POM"). On the other 049 * hand it does not handle optional for example. System paths are aligned across whole graph, making sure there is 050 * same system path used by same dependency. Finally, exclusions (exclusions are additional information not effective 051 * or applied in same POM) are always applied. This implementation makes sure, that version and scope are not applied 052 * onto same node that actually provided the rules, to no override work that ModelBuilder did. It achieves this goal 053 * by tracking "depth" for each collected rule and ignoring rules coming from same depth as processed dependency node is. 054 * <p> 055 * Note for future: the field {@code managedLocalPaths} is <em>intentionally left out of hash/equals</em>, with 056 * reason explained above. 057 * 058 * @since 2.0.0 059 */ 060public abstract class AbstractDependencyManager implements DependencyManager { 061 062 protected final int depth; 063 064 protected final int deriveUntil; 065 066 protected final int applyFrom; 067 068 protected final MMap<Key, Holder<String>> managedVersions; 069 070 protected final MMap<Key, Holder<String>> managedScopes; 071 072 protected final MMap<Key, Holder<Boolean>> managedOptionals; 073 074 protected final MMap<Key, Holder<String>> managedLocalPaths; 075 076 protected final MMap<Key, Collection<Holder<Collection<Exclusion>>>> managedExclusions; 077 078 protected final SystemDependencyScope systemDependencyScope; 079 080 private final int hashCode; 081 082 protected AbstractDependencyManager(int deriveUntil, int applyFrom, ScopeManager scopeManager) { 083 this( 084 0, 085 deriveUntil, 086 applyFrom, 087 MMap.empty(), 088 MMap.empty(), 089 MMap.empty(), 090 MMap.empty(), 091 MMap.empty(), 092 scopeManager != null 093 ? scopeManager.getSystemDependencyScope().orElse(null) 094 : SystemDependencyScope.LEGACY); 095 } 096 097 @SuppressWarnings("checkstyle:ParameterNumber") 098 protected AbstractDependencyManager( 099 int depth, 100 int deriveUntil, 101 int applyFrom, 102 MMap<Key, Holder<String>> managedVersions, 103 MMap<Key, Holder<String>> managedScopes, 104 MMap<Key, Holder<Boolean>> managedOptionals, 105 MMap<Key, Holder<String>> managedLocalPaths, 106 MMap<Key, Collection<Holder<Collection<Exclusion>>>> managedExclusions, 107 SystemDependencyScope systemDependencyScope) { 108 this.depth = depth; 109 this.deriveUntil = deriveUntil; 110 this.applyFrom = applyFrom; 111 this.managedVersions = requireNonNull(managedVersions); 112 this.managedScopes = requireNonNull(managedScopes); 113 this.managedOptionals = requireNonNull(managedOptionals); 114 this.managedLocalPaths = requireNonNull(managedLocalPaths); 115 this.managedExclusions = requireNonNull(managedExclusions); 116 // nullable: if using scope manager, but there is no system scope defined 117 this.systemDependencyScope = systemDependencyScope; 118 119 // exclude managedLocalPaths 120 this.hashCode = Objects.hash(depth, managedVersions, managedScopes, managedOptionals, managedExclusions); 121 } 122 123 protected abstract DependencyManager newInstance( 124 MMap<Key, Holder<String>> managedVersions, 125 MMap<Key, Holder<String>> managedScopes, 126 MMap<Key, Holder<Boolean>> managedOptionals, 127 MMap<Key, Holder<String>> managedLocalPaths, 128 MMap<Key, Collection<Holder<Collection<Exclusion>>>> managedExclusions); 129 130 @Override 131 public DependencyManager deriveChildManager(DependencyCollectionContext context) { 132 requireNonNull(context, "context cannot be null"); 133 if (!isDerived()) { 134 return this; 135 } 136 137 MMap<Key, Holder<String>> managedVersions = this.managedVersions; 138 MMap<Key, Holder<String>> managedScopes = this.managedScopes; 139 MMap<Key, Holder<Boolean>> managedOptionals = this.managedOptionals; 140 MMap<Key, Holder<String>> managedLocalPaths = this.managedLocalPaths; 141 MMap<Key, Collection<Holder<Collection<Exclusion>>>> managedExclusions = this.managedExclusions; 142 143 for (Dependency managedDependency : context.getManagedDependencies()) { 144 Artifact artifact = managedDependency.getArtifact(); 145 Key key = new Key(artifact); 146 147 String version = artifact.getVersion(); 148 if (!version.isEmpty() && !managedVersions.containsKey(key)) { 149 if (managedVersions == this.managedVersions) { 150 managedVersions = MMap.copy(this.managedVersions); 151 } 152 managedVersions.put(key, new Holder<>(depth, version)); 153 } 154 155 String scope = managedDependency.getScope(); 156 if (!scope.isEmpty() && !managedScopes.containsKey(key)) { 157 if (managedScopes == this.managedScopes) { 158 managedScopes = MMap.copy(this.managedScopes); 159 } 160 managedScopes.put(key, new Holder<>(depth, scope)); 161 } 162 163 Boolean optional = managedDependency.getOptional(); 164 if (optional != null && !managedOptionals.containsKey(key)) { 165 if (managedOptionals == this.managedOptionals) { 166 managedOptionals = MMap.copy(this.managedOptionals); 167 } 168 managedOptionals.put(key, new Holder<>(depth, optional)); 169 } 170 171 String localPath = systemDependencyScope == null 172 ? null 173 : systemDependencyScope.getSystemPath(managedDependency.getArtifact()); 174 if (localPath != null && !managedLocalPaths.containsKey(key)) { 175 if (managedLocalPaths == this.managedLocalPaths) { 176 managedLocalPaths = MMap.copy(this.managedLocalPaths); 177 } 178 managedLocalPaths.put(key, new Holder<>(depth, localPath)); 179 } 180 181 Collection<Exclusion> exclusions = managedDependency.getExclusions(); 182 if (!exclusions.isEmpty()) { 183 if (managedExclusions == this.managedExclusions) { 184 managedExclusions = MMap.copyWithKey(key, this.managedExclusions); 185 } 186 Collection<Holder<Collection<Exclusion>>> managed = managedExclusions.get(key); 187 if (managed == null) { 188 managed = new ArrayList<>(); 189 managedExclusions.put(key, managed); 190 } 191 managed.add(new Holder<>(depth, exclusions)); 192 } 193 } 194 195 return newInstance( 196 managedVersions.done(), 197 managedScopes.done(), 198 managedOptionals.done(), 199 managedLocalPaths.done(), 200 managedExclusions.done()); 201 } 202 203 @Override 204 public DependencyManagement manageDependency(Dependency dependency) { 205 requireNonNull(dependency, "dependency cannot be null"); 206 DependencyManagement management = null; 207 Key key = new Key(dependency.getArtifact()); 208 209 if (isApplied()) { 210 Holder<String> version = managedVersions.get(key); 211 // is managed locally by model builder 212 // apply only rules coming from "higher" levels 213 if (version != null && isApplicable(version)) { 214 management = new DependencyManagement(); 215 management.setVersion(version.getValue()); 216 } 217 218 Holder<String> scope = managedScopes.get(key); 219 // is managed locally by model builder 220 // apply only rules coming from "higher" levels 221 if (scope != null && isApplicable(scope)) { 222 if (management == null) { 223 management = new DependencyManagement(); 224 } 225 management.setScope(scope.getValue()); 226 227 if (systemDependencyScope != null 228 && !systemDependencyScope.is(scope.getValue()) 229 && systemDependencyScope.getSystemPath(dependency.getArtifact()) != null) { 230 HashMap<String, String> properties = 231 new HashMap<>(dependency.getArtifact().getProperties()); 232 systemDependencyScope.setSystemPath(properties, null); 233 management.setProperties(properties); 234 } 235 } 236 237 // system scope paths always applied to have them aligned 238 // (same artifact == same path) in whole graph 239 if (systemDependencyScope != null 240 && (scope != null && systemDependencyScope.is(scope.getValue()) 241 || (scope == null && systemDependencyScope.is(dependency.getScope())))) { 242 Holder<String> localPath = managedLocalPaths.get(key); 243 if (localPath != null) { 244 if (management == null) { 245 management = new DependencyManagement(); 246 } 247 HashMap<String, String> properties = 248 new HashMap<>(dependency.getArtifact().getProperties()); 249 systemDependencyScope.setSystemPath(properties, localPath.getValue()); 250 management.setProperties(properties); 251 } 252 } 253 254 // optional is not managed by model builder 255 // apply only rules coming from "higher" levels 256 Holder<Boolean> optional = managedOptionals.get(key); 257 if (optional != null && isApplicable(optional)) { 258 if (management == null) { 259 management = new DependencyManagement(); 260 } 261 management.setOptional(optional.getValue()); 262 } 263 } 264 265 // exclusions affect only downstream 266 // this will not "exclude" own dependency, 267 // is just added as additional information 268 // ModelBuilder does not merge exclusions (only applies if dependency does not have exclusion) 269 // so we merge it here even from same level 270 Collection<Holder<Collection<Exclusion>>> exclusions = managedExclusions.get(key); 271 if (exclusions != null) { 272 if (management == null) { 273 management = new DependencyManagement(); 274 } 275 Collection<Exclusion> result = new LinkedHashSet<>(dependency.getExclusions()); 276 for (Holder<Collection<Exclusion>> exclusion : exclusions) { 277 result.addAll(exclusion.getValue()); 278 } 279 management.setExclusions(result); 280 } 281 282 return management; 283 } 284 285 /** 286 * Returns {@code true} if current context should be factored in (collected/derived). 287 */ 288 protected boolean isDerived() { 289 return depth < deriveUntil; 290 } 291 292 /** 293 * Returns {@code true} if current dependency should be managed according to so far collected/derived rules. 294 */ 295 protected boolean isApplied() { 296 return depth >= applyFrom; 297 } 298 299 /** 300 * Returns {@code true} if rule in holder is applicable at current depth. 301 */ 302 protected boolean isApplicable(Holder<?> holder) { 303 // explanation: derive collects rules (at given depth) and then last 304 // call newInstance does depth++. This means that distance 1 is still "same node". 305 // Hence, rules from depth - 2 or above should be applied. 306 // root is special: is always applied. 307 return holder.getDepth() == 0 || depth > holder.getDepth() + 1; 308 } 309 310 @Override 311 public boolean equals(Object obj) { 312 if (this == obj) { 313 return true; 314 } else if (null == obj || !getClass().equals(obj.getClass())) { 315 return false; 316 } 317 318 AbstractDependencyManager that = (AbstractDependencyManager) obj; 319 // exclude managedLocalPaths 320 return depth == that.depth 321 && managedVersions.equals(that.managedVersions) 322 && managedScopes.equals(that.managedScopes) 323 && managedOptionals.equals(that.managedOptionals) 324 && managedExclusions.equals(that.managedExclusions); 325 } 326 327 @Override 328 public int hashCode() { 329 return hashCode; 330 } 331 332 protected static class Key { 333 private final Artifact artifact; 334 private final int hashCode; 335 336 Key(Artifact artifact) { 337 this.artifact = artifact; 338 this.hashCode = Objects.hash( 339 artifact.getArtifactId(), artifact.getGroupId(), artifact.getExtension(), artifact.getClassifier()); 340 } 341 342 @Override 343 public boolean equals(Object obj) { 344 if (obj == this) { 345 return true; 346 } else if (!(obj instanceof Key)) { 347 return false; 348 } 349 Key that = (Key) obj; 350 return artifact.getArtifactId().equals(that.artifact.getArtifactId()) 351 && artifact.getGroupId().equals(that.artifact.getGroupId()) 352 && artifact.getExtension().equals(that.artifact.getExtension()) 353 && artifact.getClassifier().equals(that.artifact.getClassifier()); 354 } 355 356 @Override 357 public int hashCode() { 358 return hashCode; 359 } 360 361 @Override 362 public String toString() { 363 return String.valueOf(artifact); 364 } 365 } 366 367 protected static class Holder<T> { 368 private final int depth; 369 private final T value; 370 private final int hashCode; 371 372 Holder(int depth, T value) { 373 this.depth = depth; 374 this.value = requireNonNull(value); 375 this.hashCode = Objects.hash(depth, value); 376 } 377 378 public int getDepth() { 379 return depth; 380 } 381 382 public T getValue() { 383 return value; 384 } 385 386 @Override 387 public boolean equals(Object o) { 388 if (!(o instanceof Holder)) { 389 return false; 390 } 391 Holder<?> holder = (Holder<?>) o; 392 return depth == holder.depth && Objects.equals(value, holder.value); 393 } 394 395 @Override 396 public int hashCode() { 397 return hashCode; 398 } 399 } 400}