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.internal.impl.collect; 020 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.Collections; 024import java.util.HashSet; 025import java.util.LinkedHashMap; 026import java.util.List; 027import java.util.Map; 028import java.util.concurrent.atomic.AtomicInteger; 029 030import org.eclipse.aether.DefaultRepositorySystemSession; 031import org.eclipse.aether.RepositoryException; 032import org.eclipse.aether.RepositorySystemSession; 033import org.eclipse.aether.RequestTrace; 034import org.eclipse.aether.artifact.Artifact; 035import org.eclipse.aether.collection.CollectRequest; 036import org.eclipse.aether.collection.CollectResult; 037import org.eclipse.aether.collection.DependencyCollectionChecker; 038import org.eclipse.aether.collection.DependencyCollectionException; 039import org.eclipse.aether.collection.DependencyGraphTransformer; 040import org.eclipse.aether.collection.DependencyTraverser; 041import org.eclipse.aether.collection.VersionFilter; 042import org.eclipse.aether.graph.DefaultDependencyNode; 043import org.eclipse.aether.graph.Dependency; 044import org.eclipse.aether.graph.DependencyNode; 045import org.eclipse.aether.impl.ArtifactDescriptorReader; 046import org.eclipse.aether.impl.DependencyCollector; 047import org.eclipse.aether.impl.RemoteRepositoryManager; 048import org.eclipse.aether.impl.VersionRangeResolver; 049import org.eclipse.aether.impl.scope.InternalScopeManager; 050import org.eclipse.aether.internal.impl.Utils; 051import org.eclipse.aether.repository.ArtifactRepository; 052import org.eclipse.aether.repository.RemoteRepository; 053import org.eclipse.aether.resolution.ArtifactDescriptorException; 054import org.eclipse.aether.resolution.ArtifactDescriptorRequest; 055import org.eclipse.aether.resolution.ArtifactDescriptorResult; 056import org.eclipse.aether.resolution.VersionRangeRequest; 057import org.eclipse.aether.resolution.VersionRangeResolutionException; 058import org.eclipse.aether.resolution.VersionRangeResult; 059import org.eclipse.aether.scope.ResolutionScope; 060import org.eclipse.aether.scope.SystemDependencyScope; 061import org.eclipse.aether.spi.artifact.decorator.ArtifactDecorator; 062import org.eclipse.aether.spi.artifact.decorator.ArtifactDecoratorFactory; 063import org.eclipse.aether.util.ConfigUtils; 064import org.eclipse.aether.util.graph.transformer.TransformationContextKeys; 065import org.eclipse.aether.version.Version; 066import org.slf4j.Logger; 067import org.slf4j.LoggerFactory; 068 069import static java.util.Objects.requireNonNull; 070 071/** 072 * Helper class for delegate implementations, they MUST subclass this class. 073 * 074 * @since 1.8.0 075 */ 076public abstract class DependencyCollectorDelegate implements DependencyCollector { 077 /** 078 * Only exceptions up to the number given in this configuration property are emitted. Exceptions which exceed 079 * that number are swallowed. 080 * 081 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 082 * @configurationType {@link java.lang.Integer} 083 * @configurationDefaultValue {@link #DEFAULT_MAX_EXCEPTIONS} 084 */ 085 public static final String CONFIG_PROP_MAX_EXCEPTIONS = 086 DefaultDependencyCollector.CONFIG_PROPS_PREFIX + "maxExceptions"; 087 088 public static final int DEFAULT_MAX_EXCEPTIONS = 50; 089 090 /** 091 * Only up to the given amount cyclic dependencies are emitted. 092 * 093 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 094 * @configurationType {@link java.lang.Integer} 095 * @configurationDefaultValue {@link #DEFAULT_MAX_CYCLES} 096 */ 097 public static final String CONFIG_PROP_MAX_CYCLES = DefaultDependencyCollector.CONFIG_PROPS_PREFIX + "maxCycles"; 098 099 public static final int DEFAULT_MAX_CYCLES = 10; 100 101 /** 102 * The allowed runs of collection (and re-collection). By default, there must be at least 1 run, so values smaller 103 * than 1 are considered configuration errors and will fail dependency collection. 104 * 105 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 106 * @configurationType {@link java.lang.Integer} 107 * @configurationDefaultValue {@link #DEFAULT_MAX_RUNS} 108 * @since 2.0.19 109 */ 110 public static final String CONFIG_PROP_MAX_RUNS = DefaultDependencyCollector.CONFIG_PROPS_PREFIX + "maxRuns"; 111 112 public static final int DEFAULT_MAX_RUNS = 5; 113 114 protected final Logger logger = LoggerFactory.getLogger(getClass()); 115 116 protected final RemoteRepositoryManager remoteRepositoryManager; 117 118 protected final ArtifactDescriptorReader descriptorReader; 119 120 protected final VersionRangeResolver versionRangeResolver; 121 122 protected final Map<String, ArtifactDecoratorFactory> artifactDecoratorFactories; 123 124 protected DependencyCollectorDelegate( 125 RemoteRepositoryManager remoteRepositoryManager, 126 ArtifactDescriptorReader artifactDescriptorReader, 127 VersionRangeResolver versionRangeResolver, 128 Map<String, ArtifactDecoratorFactory> artifactDecoratorFactories) { 129 this.remoteRepositoryManager = 130 requireNonNull(remoteRepositoryManager, "remote repository manager cannot be null"); 131 this.descriptorReader = requireNonNull(artifactDescriptorReader, "artifact descriptor reader cannot be null"); 132 this.versionRangeResolver = requireNonNull(versionRangeResolver, "version range resolver cannot be null"); 133 this.artifactDecoratorFactories = 134 requireNonNull(artifactDecoratorFactories, "artifact decorator factories cannot be null"); 135 } 136 137 @SuppressWarnings("checkstyle:methodlength") 138 @Override 139 public final CollectResult collectDependencies( 140 final RepositorySystemSession originalSession, final CollectRequest request) 141 throws DependencyCollectionException { 142 requireNonNull(originalSession, "session cannot be null"); 143 requireNonNull(request, "request cannot be null"); 144 145 final InternalScopeManager scopeManager = (InternalScopeManager) originalSession.getScopeManager(); 146 final RepositorySystemSession setUpSession = setUpSession(originalSession, request, scopeManager); 147 final DependencyCollectionChecker dependencyCollectionChecker = 148 originalSession.getDependencyCollectionChecker() == null 149 ? DependencyCollectionChecker.NOOP 150 : originalSession.getDependencyCollectionChecker(); 151 152 final RequestTrace trace = RequestTrace.newChild(request.getTrace(), request); 153 154 final Map<String, Object> stats = new LinkedHashMap<>(); 155 final AtomicInteger runs = new AtomicInteger(0); 156 final int maxRuns = ConfigUtils.getInteger(originalSession, DEFAULT_MAX_RUNS, CONFIG_PROP_MAX_RUNS); 157 if (maxRuns < 1) { 158 throw new DependencyCollectionException( 159 new CollectResult(request), 160 "Invalid configuration: '" + CONFIG_PROP_MAX_RUNS 161 + "' configuration must be equal or grater than 1"); 162 } 163 164 CollectResult result = null; 165 166 boolean finished = false; 167 while (!finished) { 168 final long time1 = System.nanoTime(); 169 if (runs.incrementAndGet() > maxRuns) { 170 throw new DependencyCollectionException( 171 new CollectResult(request), 172 "Too many collection attempts (bug of used DependencyCollectionChecker?)"); 173 } 174 175 RepositorySystemSession session = dependencyCollectionChecker.prepare(setUpSession, request); 176 final DependencyTraverser depTraverser = session.getDependencyTraverser(); 177 final VersionFilter verFilter = session.getVersionFilter(); 178 179 Dependency root = request.getRoot(); 180 List<RemoteRepository> repositories = request.getRepositories(); 181 List<Dependency> dependencies = request.getDependencies(); 182 List<Dependency> managedDependencies = request.getManagedDependencies(); 183 184 result = new CollectResult(request); 185 DefaultDependencyNode node; 186 if (root != null) { 187 List<? extends Version> versions; 188 VersionRangeResult rangeResult; 189 try { 190 VersionRangeRequest rangeRequest = new VersionRangeRequest( 191 root.getArtifact(), request.getRepositories(), request.getRequestContext()); 192 rangeRequest.setTrace(trace); 193 rangeResult = versionRangeResolver.resolveVersionRange(session, rangeRequest); 194 versions = filterVersions(root, rangeResult, verFilter, new DefaultVersionFilterContext(session)); 195 } catch (VersionRangeResolutionException e) { 196 result.addException(e); 197 throw new DependencyCollectionException(result, e.getMessage()); 198 } 199 200 Version version = versions.get(versions.size() - 1); 201 root = root.setArtifact(root.getArtifact().setVersion(version.toString())); 202 203 ArtifactDescriptorResult descriptorResult; 204 try { 205 ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest(); 206 descriptorRequest.setArtifact(root.getArtifact()); 207 descriptorRequest.setRepositories(request.getRepositories()); 208 descriptorRequest.setRequestContext(request.getRequestContext()); 209 descriptorRequest.setTrace(trace); 210 if (isLackingDescriptor(session, root.getArtifact())) { 211 descriptorResult = new ArtifactDescriptorResult(descriptorRequest); 212 } else { 213 descriptorResult = descriptorReader.readArtifactDescriptor(session, descriptorRequest); 214 for (ArtifactDecorator decorator : 215 Utils.getArtifactDecorators(session, artifactDecoratorFactories)) { 216 descriptorResult.setArtifact(decorator.decorateArtifact(descriptorResult)); 217 } 218 } 219 } catch (ArtifactDescriptorException e) { 220 result.addException(e); 221 throw new DependencyCollectionException(result, e.getMessage()); 222 } 223 224 root = root.setArtifact(descriptorResult.getArtifact()); 225 226 if (!session.isIgnoreArtifactDescriptorRepositories()) { 227 repositories = remoteRepositoryManager.aggregateRepositories( 228 session, repositories, descriptorResult.getRepositories(), true); 229 } 230 dependencies = mergeDeps(dependencies, descriptorResult.getDependencies()); 231 managedDependencies = mergeDeps(managedDependencies, descriptorResult.getManagedDependencies()); 232 233 node = new DefaultDependencyNode(root); 234 node.setRequestContext(request.getRequestContext()); 235 node.setRelocations(descriptorResult.getRelocations()); 236 node.setVersionConstraint(rangeResult.getVersionConstraint()); 237 node.setVersion(version); 238 node.setAliases(descriptorResult.getAliases()); 239 node.setRepositories(request.getRepositories()); 240 } else { 241 node = new DefaultDependencyNode(request.getRootArtifact()); 242 node.setRequestContext(request.getRequestContext()); 243 node.setRepositories(request.getRepositories()); 244 } 245 246 result.setRoot(node); 247 248 boolean traverse = root == null || depTraverser == null || depTraverser.traverseDependency(root); 249 String errorPath = null; 250 if (traverse && !dependencies.isEmpty()) { 251 DataPool pool = new DataPool(session); 252 253 DefaultDependencyCollectionContext context = new DefaultDependencyCollectionContext( 254 session, request.getRootArtifact(), root, managedDependencies); 255 256 DefaultVersionFilterContext versionContext = new DefaultVersionFilterContext(session); 257 258 Results results = new Results(result, session); 259 260 doCollectDependencies( 261 session, 262 trace, 263 pool, 264 context, 265 versionContext, 266 request, 267 node, 268 repositories, 269 dependencies, 270 managedDependencies, 271 results); 272 273 errorPath = results.getErrorPath(); 274 } 275 276 final long time2 = System.nanoTime(); 277 278 DependencyGraphTransformer transformer = session.getDependencyGraphTransformer(); 279 if (transformer != null) { 280 try { 281 DefaultDependencyGraphTransformationContext context = 282 new DefaultDependencyGraphTransformationContext(session); 283 context.put(TransformationContextKeys.STATS, stats); 284 result.setRoot(transformer.transformGraph(node, context)); 285 } catch (RepositoryException e) { 286 result.addException(e); 287 } 288 } 289 290 if (errorPath != null) { 291 throw new DependencyCollectionException(result, "Failed to collect dependencies at " + errorPath); 292 } 293 if (!result.getExceptions().isEmpty()) { 294 throw new DependencyCollectionException(result); 295 } 296 297 if (request.getResolutionScope() != null) { 298 result = scopeManager.postProcess(session, request.getResolutionScope(), result); 299 } 300 301 long time3 = System.nanoTime(); 302 stats.put(getClass().getSimpleName() + ".collectTime", time2 - time1); 303 stats.put(getClass().getSimpleName() + ".transformTime", time3 - time2); 304 305 finished = dependencyCollectionChecker.isSatisfactory(session, request, result); 306 } 307 308 stats.put(getClass().getSimpleName() + ".runs", runs.get()); 309 if (logger.isDebugEnabled()) { 310 logger.debug("Dependency collection stats {}", stats); 311 } 312 313 return result; 314 } 315 316 /** 317 * Creates child {@link RequestTrace} instance from passed in {@link RequestTrace} and parameters by creating 318 * {@link CollectStepDataImpl} instance out of passed in data. Caller must ensure that passed in parameters are 319 * NOT affected by threading (or that there is no multi threading involved). In other words, the passed in values 320 * should be immutable. 321 * 322 * @param trace The current trace instance. 323 * @param context The context from {@link CollectRequest#getRequestContext()}, never {@code null}. 324 * @param path List representing the path of dependency nodes, never {@code null}. Caller must ensure, that this 325 * list does not change during the lifetime of the requested {@link RequestTrace} instance. If it may 326 * change, simplest is to pass here a copy of used list. 327 * @param node Currently collected node, that collector came by following the passed in path. 328 * @return A child request trance instance, never {@code null}. 329 */ 330 protected RequestTrace collectStepTrace( 331 RequestTrace trace, String context, List<DependencyNode> path, Dependency node) { 332 return RequestTrace.newChild(trace, new CollectStepDataImpl(context, path, node)); 333 } 334 335 @SuppressWarnings("checkstyle:parameternumber") 336 protected abstract void doCollectDependencies( 337 RepositorySystemSession session, 338 RequestTrace trace, 339 DataPool pool, 340 DefaultDependencyCollectionContext context, 341 DefaultVersionFilterContext versionContext, 342 CollectRequest request, 343 DependencyNode node, 344 List<RemoteRepository> repositories, 345 List<Dependency> dependencies, 346 List<Dependency> managedDependencies, 347 Results results) 348 throws DependencyCollectionException; 349 350 protected RepositorySystemSession setUpSession( 351 RepositorySystemSession session, CollectRequest collectRequest, InternalScopeManager scopeManager) { 352 DefaultRepositorySystemSession optimized = new DefaultRepositorySystemSession(session); 353 optimized.setArtifactTypeRegistry(CachingArtifactTypeRegistry.newInstance(session)); 354 355 ResolutionScope resolutionScope = collectRequest.getResolutionScope(); 356 if (resolutionScope != null) { 357 requireNonNull(scopeManager, "ScopeManager is not set on session"); 358 optimized.setDependencySelector(scopeManager.getDependencySelector(session, resolutionScope)); 359 } 360 return optimized; 361 } 362 363 protected List<Dependency> mergeDeps(List<Dependency> dominant, List<Dependency> recessive) { 364 List<Dependency> result; 365 if (dominant == null || dominant.isEmpty()) { 366 result = recessive; 367 } else if (recessive == null || recessive.isEmpty()) { 368 result = dominant; 369 } else { 370 int initialCapacity = dominant.size() + recessive.size(); 371 result = new ArrayList<>(initialCapacity); 372 Collection<String> ids = new HashSet<>(initialCapacity, 1.0f); 373 for (Dependency dependency : dominant) { 374 ids.add(getId(dependency.getArtifact())); 375 result.add(dependency); 376 } 377 for (Dependency dependency : recessive) { 378 if (!ids.contains(getId(dependency.getArtifact()))) { 379 result.add(dependency); 380 } 381 } 382 } 383 return result; 384 } 385 386 protected static String getId(Artifact a) { 387 return a.getGroupId() + ':' + a.getArtifactId() + ':' + a.getClassifier() + ':' + a.getExtension(); 388 } 389 390 @SuppressWarnings("checkstyle:parameternumber") 391 protected static DefaultDependencyNode createDependencyNode( 392 List<Artifact> relocations, 393 PremanagedDependency preManaged, 394 VersionRangeResult rangeResult, 395 Version version, 396 Dependency d, 397 Collection<Artifact> aliases, 398 List<RemoteRepository> repos, 399 String requestContext) { 400 DefaultDependencyNode child = new DefaultDependencyNode(d); 401 preManaged.applyTo(child); 402 child.setRelocations(relocations); 403 child.setVersionConstraint(rangeResult.getVersionConstraint()); 404 child.setVersion(version); 405 child.setAliases(aliases); 406 child.setRepositories(repos); 407 child.setRequestContext(requestContext); 408 return child; 409 } 410 411 protected static DefaultDependencyNode createDependencyNode( 412 List<Artifact> relocations, 413 PremanagedDependency preManaged, 414 VersionRangeResult rangeResult, 415 Version version, 416 Dependency d, 417 ArtifactDescriptorResult descriptorResult, 418 DependencyNode cycleNode) { 419 DefaultDependencyNode child = createDependencyNode( 420 relocations, 421 preManaged, 422 rangeResult, 423 version, 424 d, 425 descriptorResult.getAliases(), 426 cycleNode.getRepositories(), 427 cycleNode.getRequestContext()); 428 child.setChildren(cycleNode.getChildren()); 429 return child; 430 } 431 432 protected static ArtifactDescriptorRequest createArtifactDescriptorRequest( 433 String requestContext, RequestTrace requestTrace, List<RemoteRepository> repositories, Dependency d) { 434 ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest(); 435 descriptorRequest.setArtifact(d.getArtifact()); 436 descriptorRequest.setRepositories(repositories); 437 descriptorRequest.setRequestContext(requestContext); 438 descriptorRequest.setTrace(requestTrace); 439 return descriptorRequest; 440 } 441 442 protected static VersionRangeRequest createVersionRangeRequest( 443 String requestContext, 444 RequestTrace requestTrace, 445 List<RemoteRepository> repositories, 446 Dependency dependency) { 447 VersionRangeRequest rangeRequest = new VersionRangeRequest(); 448 rangeRequest.setArtifact(dependency.getArtifact()); 449 rangeRequest.setRepositories(repositories); 450 rangeRequest.setRequestContext(requestContext); 451 rangeRequest.setTrace(requestTrace); 452 return rangeRequest; 453 } 454 455 protected VersionRangeResult cachedResolveRangeResult( 456 VersionRangeRequest rangeRequest, DataPool pool, RepositorySystemSession session) 457 throws VersionRangeResolutionException { 458 Object key = pool.toKey(rangeRequest); 459 VersionRangeResult rangeResult = pool.getConstraint(key, rangeRequest); 460 if (rangeResult == null) { 461 rangeResult = versionRangeResolver.resolveVersionRange(session, rangeRequest); 462 pool.putConstraint(key, rangeResult); 463 } 464 return rangeResult; 465 } 466 467 protected static boolean isLackingDescriptor(RepositorySystemSession session, Artifact artifact) { 468 SystemDependencyScope systemDependencyScope = session.getSystemDependencyScope(); 469 return systemDependencyScope != null && systemDependencyScope.getSystemPath(artifact) != null; 470 } 471 472 protected static List<RemoteRepository> getRemoteRepositories( 473 ArtifactRepository repository, List<RemoteRepository> repositories) { 474 if (repository instanceof RemoteRepository) { 475 return Collections.singletonList((RemoteRepository) repository); 476 } 477 if (repository != null) { 478 return Collections.emptyList(); 479 } 480 return repositories; 481 } 482 483 protected static List<? extends Version> filterVersions( 484 Dependency dependency, 485 VersionRangeResult rangeResult, 486 VersionFilter verFilter, 487 DefaultVersionFilterContext verContext) 488 throws VersionRangeResolutionException { 489 if (rangeResult.getVersions().isEmpty()) { 490 throw new VersionRangeResolutionException( 491 rangeResult, "No versions available for " + dependency.getArtifact() + " within specified range"); 492 } 493 494 List<? extends Version> versions; 495 if (verFilter != null && rangeResult.getVersionConstraint().getRange() != null) { 496 verContext = verContext.initialize(dependency, rangeResult); 497 try { 498 verFilter.filterVersions(verContext); 499 } catch (RepositoryException e) { 500 throw new VersionRangeResolutionException( 501 rangeResult, "Failed to filter versions for " + dependency.getArtifact(), e); 502 } 503 versions = verContext.get(); 504 if (versions.isEmpty()) { 505 throw new VersionRangeResolutionException( 506 rangeResult, 507 "No acceptable versions for " + dependency.getArtifact() + ": " + rangeResult.getVersions()); 508 } 509 } else { 510 versions = rangeResult.getVersions(); 511 } 512 return versions; 513 } 514 515 protected ArtifactDescriptorResult resolveCachedArtifactDescriptor( 516 DataPool pool, 517 ArtifactDescriptorRequest descriptorRequest, 518 RepositorySystemSession session, 519 Dependency d, 520 Results results, 521 List<DependencyNode> nodes) { 522 DataPool.DescriptorKey key = pool.toKey(descriptorRequest); 523 ArtifactDescriptorResult descriptorResult = pool.getDescriptor(key, descriptorRequest); 524 if (descriptorResult == null) { 525 try { 526 descriptorResult = descriptorReader.readArtifactDescriptor(session, descriptorRequest); 527 for (ArtifactDecorator decorator : Utils.getArtifactDecorators(session, artifactDecoratorFactories)) { 528 descriptorResult.setArtifact(decorator.decorateArtifact(descriptorResult)); 529 } 530 pool.putDescriptor(key, descriptorResult); 531 } catch (ArtifactDescriptorException e) { 532 results.addException(d, e, nodes); 533 pool.putDescriptor(key, e); 534 return null; 535 } 536 } else if (descriptorResult == DataPool.NO_DESCRIPTOR) { 537 return null; 538 } 539 return descriptorResult; 540 } 541 542 /** 543 * Helper class used during collection. 544 */ 545 protected static class Results { 546 547 private final CollectResult result; 548 549 final int maxExceptions; 550 551 final int maxCycles; 552 553 volatile String errorPath; 554 555 public Results(CollectResult result, RepositorySystemSession session) { 556 this.result = result; 557 558 maxExceptions = ConfigUtils.getInteger(session, DEFAULT_MAX_EXCEPTIONS, CONFIG_PROP_MAX_EXCEPTIONS); 559 560 maxCycles = ConfigUtils.getInteger(session, DEFAULT_MAX_CYCLES, CONFIG_PROP_MAX_CYCLES); 561 } 562 563 public synchronized CollectResult getResult() { 564 return result; 565 } 566 567 public synchronized String getErrorPath() { 568 return errorPath; 569 } 570 571 public synchronized void addException(Dependency dependency, Exception e, List<DependencyNode> nodes) { 572 if (maxExceptions < 0 || result.getExceptions().size() < maxExceptions) { 573 result.addException(e); 574 if (errorPath == null) { 575 StringBuilder buffer = new StringBuilder(256); 576 for (DependencyNode node : nodes) { 577 if (buffer.length() > 0) { 578 buffer.append(" -> "); 579 } 580 Dependency dep = node.getDependency(); 581 if (dep != null) { 582 buffer.append(dep.getArtifact()); 583 } 584 } 585 if (buffer.length() > 0) { 586 buffer.append(" -> "); 587 } 588 buffer.append(dependency.getArtifact()); 589 errorPath = buffer.toString(); 590 } 591 } 592 } 593 594 public synchronized void addCycle(List<DependencyNode> nodes, int cycleEntry, Dependency dependency) { 595 if (maxCycles < 0 || result.getCycles().size() < maxCycles) { 596 result.addCycle(new DefaultDependencyCycle(nodes, cycleEntry, dependency)); 597 } 598 } 599 } 600}