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 * <p> 058 * Implementation note for all managers extending this class: this class maintains "path" (list of parent managers) 059 * and "depth". Depth {@code 0} is basically used as "factory" on session; is the instance created during session 060 * creation and is usually empty (just parameterized). Depth 1 is the current collection "root", depth 2 061 * are direct dependencies, depth 3 first level of transitive dependencies of direct dependencies and so on. Hence, on 062 * depth 1 (the collection root, initialized with management possibly as well) parent will be always the empty "factory" 063 * instance, and we need special handling: "apply onto itself". This does not stand on depth > 1. 064 * 065 * @since 2.0.0 066 */ 067public abstract class AbstractDependencyManager implements DependencyManager { 068 protected final ArrayList<AbstractDependencyManager> path; 069 070 protected final int depth; 071 072 protected final int deriveUntil; 073 074 protected final int applyFrom; 075 076 protected final MMap<Key, String> managedVersions; 077 078 protected final MMap<Key, String> managedScopes; 079 080 protected final MMap<Key, Boolean> managedOptionals; 081 082 protected final MMap<Key, String> managedLocalPaths; 083 084 protected final MMap<Key, Holder<Collection<Exclusion>>> managedExclusions; 085 086 protected final SystemDependencyScope systemDependencyScope; 087 088 private final int hashCode; 089 090 protected AbstractDependencyManager(int deriveUntil, int applyFrom, ScopeManager scopeManager) { 091 this( 092 new ArrayList<>(), 093 0, 094 deriveUntil, 095 applyFrom, 096 null, 097 null, 098 null, 099 null, 100 null, 101 scopeManager != null 102 ? scopeManager.getSystemDependencyScope().orElse(null) 103 : SystemDependencyScope.LEGACY); 104 } 105 106 @SuppressWarnings("checkstyle:ParameterNumber") 107 protected AbstractDependencyManager( 108 ArrayList<AbstractDependencyManager> path, 109 int depth, 110 int deriveUntil, 111 int applyFrom, 112 MMap<Key, String> managedVersions, 113 MMap<Key, String> managedScopes, 114 MMap<Key, Boolean> managedOptionals, 115 MMap<Key, String> managedLocalPaths, 116 MMap<Key, Holder<Collection<Exclusion>>> managedExclusions, 117 SystemDependencyScope systemDependencyScope) { 118 this.path = path; 119 this.depth = depth; 120 this.deriveUntil = deriveUntil; 121 this.applyFrom = applyFrom; 122 this.managedVersions = managedVersions; 123 this.managedScopes = managedScopes; 124 this.managedOptionals = managedOptionals; 125 this.managedLocalPaths = managedLocalPaths; 126 this.managedExclusions = managedExclusions; 127 // nullable: if using scope manager, but there is no system scope defined 128 this.systemDependencyScope = systemDependencyScope; 129 130 // exclude managedLocalPaths 131 this.hashCode = Objects.hash(path, depth, managedVersions, managedScopes, managedOptionals, managedExclusions); 132 } 133 134 protected abstract DependencyManager newInstance( 135 MMap<Key, String> managedVersions, 136 MMap<Key, String> managedScopes, 137 MMap<Key, Boolean> managedOptionals, 138 MMap<Key, String> managedLocalPaths, 139 MMap<Key, Holder<Collection<Exclusion>>> managedExclusions); 140 141 private boolean containsManagedVersion(Key key) { 142 for (AbstractDependencyManager ancestor : path) { 143 if (ancestor.managedVersions != null && ancestor.managedVersions.containsKey(key)) { 144 return true; 145 } 146 } 147 return managedVersions != null && managedVersions.containsKey(key); 148 } 149 150 private String getManagedVersion(Key key) { 151 for (AbstractDependencyManager ancestor : path) { 152 if (ancestor.managedVersions != null && ancestor.managedVersions.containsKey(key)) { 153 return ancestor.managedVersions.get(key); 154 } 155 } 156 if (depth == 1 && managedVersions != null && managedVersions.containsKey(key)) { 157 return managedVersions.get(key); 158 } 159 return null; 160 } 161 162 private boolean containsManagedScope(Key key) { 163 for (AbstractDependencyManager ancestor : path) { 164 if (ancestor.managedScopes != null && ancestor.managedScopes.containsKey(key)) { 165 return true; 166 } 167 } 168 return managedScopes != null && managedScopes.containsKey(key); 169 } 170 171 private String getManagedScope(Key key) { 172 for (AbstractDependencyManager ancestor : path) { 173 if (ancestor.managedScopes != null && ancestor.managedScopes.containsKey(key)) { 174 return ancestor.managedScopes.get(key); 175 } 176 } 177 if (depth == 1 && managedScopes != null && managedScopes.containsKey(key)) { 178 return managedScopes.get(key); 179 } 180 return null; 181 } 182 183 private boolean containsManagedOptional(Key key) { 184 for (AbstractDependencyManager ancestor : path) { 185 if (ancestor.managedOptionals != null && ancestor.managedOptionals.containsKey(key)) { 186 return true; 187 } 188 } 189 return managedOptionals != null && managedOptionals.containsKey(key); 190 } 191 192 private Boolean getManagedOptional(Key key) { 193 for (AbstractDependencyManager ancestor : path) { 194 if (ancestor.managedOptionals != null && ancestor.managedOptionals.containsKey(key)) { 195 return ancestor.managedOptionals.get(key); 196 } 197 } 198 if (depth == 1 && managedOptionals != null && managedOptionals.containsKey(key)) { 199 return managedOptionals.get(key); 200 } 201 return null; 202 } 203 204 private boolean containsManagedLocalPath(Key key) { 205 for (AbstractDependencyManager ancestor : path) { 206 if (ancestor.managedLocalPaths != null && ancestor.managedLocalPaths.containsKey(key)) { 207 return true; 208 } 209 } 210 return managedLocalPaths != null && managedLocalPaths.containsKey(key); 211 } 212 213 private String getManagedLocalPath(Key key) { 214 for (AbstractDependencyManager ancestor : path) { 215 if (ancestor.managedLocalPaths != null && ancestor.managedLocalPaths.containsKey(key)) { 216 return ancestor.managedLocalPaths.get(key); 217 } 218 } 219 if (managedLocalPaths != null && managedLocalPaths.containsKey(key)) { 220 return managedLocalPaths.get(key); 221 } 222 return null; 223 } 224 225 /** 226 * Merges all way down. 227 */ 228 private Collection<Exclusion> getManagedExclusions(Key key) { 229 ArrayList<Exclusion> result = new ArrayList<>(); 230 for (AbstractDependencyManager ancestor : path) { 231 if (ancestor.managedExclusions != null && ancestor.managedExclusions.containsKey(key)) { 232 result.addAll(ancestor.managedExclusions.get(key).value); 233 } 234 } 235 if (managedExclusions != null && managedExclusions.containsKey(key)) { 236 result.addAll(managedExclusions.get(key).value); 237 } 238 return result.isEmpty() ? null : result; 239 } 240 241 @Override 242 public DependencyManager deriveChildManager(DependencyCollectionContext context) { 243 requireNonNull(context, "context cannot be null"); 244 if (!isDerived()) { 245 return this; 246 } 247 248 MMap<Key, String> managedVersions = null; 249 MMap<Key, String> managedScopes = null; 250 MMap<Key, Boolean> managedOptionals = null; 251 MMap<Key, String> managedLocalPaths = null; 252 MMap<Key, Holder<Collection<Exclusion>>> managedExclusions = null; 253 254 for (Dependency managedDependency : context.getManagedDependencies()) { 255 Artifact artifact = managedDependency.getArtifact(); 256 Key key = new Key(artifact); 257 258 String version = artifact.getVersion(); 259 if (!version.isEmpty() && !containsManagedVersion(key)) { 260 if (managedVersions == null) { 261 managedVersions = MMap.emptyNotDone(); 262 } 263 managedVersions.put(key, version); 264 } 265 266 String scope = managedDependency.getScope(); 267 if (!scope.isEmpty() && !containsManagedScope(key)) { 268 if (managedScopes == null) { 269 managedScopes = MMap.emptyNotDone(); 270 } 271 managedScopes.put(key, scope); 272 } 273 274 Boolean optional = managedDependency.getOptional(); 275 if (optional != null && !containsManagedOptional(key)) { 276 if (managedOptionals == null) { 277 managedOptionals = MMap.emptyNotDone(); 278 } 279 managedOptionals.put(key, optional); 280 } 281 282 String localPath = systemDependencyScope == null 283 ? null 284 : systemDependencyScope.getSystemPath(managedDependency.getArtifact()); 285 if (localPath != null && !containsManagedLocalPath(key)) { 286 if (managedLocalPaths == null) { 287 managedLocalPaths = MMap.emptyNotDone(); 288 } 289 managedLocalPaths.put(key, localPath); 290 } 291 292 Collection<Exclusion> exclusions = managedDependency.getExclusions(); 293 if (!exclusions.isEmpty()) { 294 if (managedExclusions == null) { 295 managedExclusions = MMap.emptyNotDone(); 296 } 297 Holder<Collection<Exclusion>> managed = managedExclusions.get(key); 298 if (managed != null) { 299 ArrayList<Exclusion> ex = new ArrayList<>(managed.getValue()); 300 ex.addAll(exclusions); 301 managed = new Holder<>(ex); 302 managedExclusions.put(key, managed); 303 } else { 304 managedExclusions.put(key, new Holder<>(exclusions)); 305 } 306 } 307 } 308 309 return newInstance( 310 managedVersions != null ? managedVersions.done() : null, 311 managedScopes != null ? managedScopes.done() : null, 312 managedOptionals != null ? managedOptionals.done() : null, 313 managedLocalPaths != null ? managedLocalPaths.done() : null, 314 managedExclusions != null ? managedExclusions.done() : null); 315 } 316 317 @Override 318 public DependencyManagement manageDependency(Dependency dependency) { 319 requireNonNull(dependency, "dependency cannot be null"); 320 DependencyManagement management = null; 321 Key key = new Key(dependency.getArtifact()); 322 323 if (isApplied()) { 324 String version = getManagedVersion(key); 325 // is managed locally by model builder 326 // apply only rules coming from "higher" levels 327 if (version != null) { 328 management = new DependencyManagement(); 329 management.setVersion(version); 330 } 331 332 String scope = getManagedScope(key); 333 // is managed locally by model builder 334 // apply only rules coming from "higher" levels 335 if (scope != null) { 336 if (management == null) { 337 management = new DependencyManagement(); 338 } 339 management.setScope(scope); 340 341 if (systemDependencyScope != null 342 && !systemDependencyScope.is(scope) 343 && systemDependencyScope.getSystemPath(dependency.getArtifact()) != null) { 344 HashMap<String, String> properties = 345 new HashMap<>(dependency.getArtifact().getProperties()); 346 systemDependencyScope.setSystemPath(properties, null); 347 management.setProperties(properties); 348 } 349 } 350 351 // system scope paths always applied to have them aligned 352 // (same artifact == same path) in whole graph 353 if (systemDependencyScope != null 354 && (scope != null && systemDependencyScope.is(scope) 355 || (scope == null && systemDependencyScope.is(dependency.getScope())))) { 356 String localPath = getManagedLocalPath(key); 357 if (localPath != null) { 358 if (management == null) { 359 management = new DependencyManagement(); 360 } 361 HashMap<String, String> properties = 362 new HashMap<>(dependency.getArtifact().getProperties()); 363 systemDependencyScope.setSystemPath(properties, localPath); 364 management.setProperties(properties); 365 } 366 } 367 368 // optional is not managed by model builder 369 // apply only rules coming from "higher" levels 370 Boolean optional = getManagedOptional(key); 371 if (optional != null) { 372 if (management == null) { 373 management = new DependencyManagement(); 374 } 375 management.setOptional(optional); 376 } 377 } 378 379 // exclusions affect only downstream 380 // this will not "exclude" own dependency, 381 // is just added as additional information 382 // ModelBuilder does not merge exclusions (only applies if dependency does not have exclusion) 383 // so we merge it here even from same level 384 Collection<Exclusion> exclusions = getManagedExclusions(key); 385 if (exclusions != null) { 386 if (management == null) { 387 management = new DependencyManagement(); 388 } 389 Collection<Exclusion> result = new LinkedHashSet<>(dependency.getExclusions()); 390 result.addAll(exclusions); 391 management.setExclusions(result); 392 } 393 394 return management; 395 } 396 397 /** 398 * Returns {@code true} if current context should be factored in (collected/derived). 399 */ 400 protected boolean isDerived() { 401 return depth < deriveUntil; 402 } 403 404 /** 405 * Returns {@code true} if current dependency should be managed according to so far collected/derived rules. 406 */ 407 protected boolean isApplied() { 408 return depth >= applyFrom; 409 } 410 411 @Override 412 public boolean equals(Object obj) { 413 if (this == obj) { 414 return true; 415 } else if (null == obj || !getClass().equals(obj.getClass())) { 416 return false; 417 } 418 419 AbstractDependencyManager that = (AbstractDependencyManager) obj; 420 // exclude managedLocalPaths 421 return Objects.equals(path, that.path) 422 && depth == that.depth 423 && Objects.equals(managedVersions, that.managedVersions) 424 && Objects.equals(managedScopes, that.managedScopes) 425 && Objects.equals(managedOptionals, that.managedOptionals) 426 && Objects.equals(managedExclusions, that.managedExclusions); 427 } 428 429 @Override 430 public int hashCode() { 431 return hashCode; 432 } 433 434 protected static class Key { 435 private final Artifact artifact; 436 private final int hashCode; 437 438 Key(Artifact artifact) { 439 this.artifact = artifact; 440 this.hashCode = Objects.hash( 441 artifact.getArtifactId(), artifact.getGroupId(), artifact.getExtension(), artifact.getClassifier()); 442 } 443 444 @Override 445 public boolean equals(Object obj) { 446 if (obj == this) { 447 return true; 448 } else if (!(obj instanceof Key)) { 449 return false; 450 } 451 Key that = (Key) obj; 452 return artifact.getArtifactId().equals(that.artifact.getArtifactId()) 453 && artifact.getGroupId().equals(that.artifact.getGroupId()) 454 && artifact.getExtension().equals(that.artifact.getExtension()) 455 && artifact.getClassifier().equals(that.artifact.getClassifier()); 456 } 457 458 @Override 459 public int hashCode() { 460 return hashCode; 461 } 462 463 @Override 464 public String toString() { 465 return String.valueOf(artifact); 466 } 467 } 468 469 /** 470 * Wrapper class for collection to memoize hash code. 471 * 472 * @param <T> The collection type. 473 */ 474 protected static class Holder<T> { 475 private final T value; 476 private final int hashCode; 477 478 Holder(T value) { 479 this.value = requireNonNull(value); 480 this.hashCode = Objects.hash(value); 481 } 482 483 public T getValue() { 484 return value; 485 } 486 487 @Override 488 public boolean equals(Object o) { 489 if (!(o instanceof Holder)) { 490 return false; 491 } 492 Holder<?> holder = (Holder<?>) o; 493 return Objects.equals(value, holder.value); 494 } 495 496 @Override 497 public int hashCode() { 498 return hashCode; 499 } 500 } 501}