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.Arrays; 022import java.util.Collections; 023import java.util.List; 024 025import org.eclipse.aether.artifact.Artifact; 026import org.eclipse.aether.graph.Dependency; 027import org.eclipse.aether.graph.DependencyCycle; 028import org.eclipse.aether.graph.DependencyNode; 029import org.eclipse.aether.util.artifact.ArtifactIdUtils; 030 031/** 032 * Default implementation of {@link DependencyCycle}. 033 * Internal helper class for collector implementations. 034 */ 035public final class DefaultDependencyCycle implements DependencyCycle { 036 private final List<Dependency> dependencies; 037 038 private final int cycleEntry; 039 040 public DefaultDependencyCycle(List<DependencyNode> nodes, int cycleEntry, Dependency dependency) { 041 // skip root node unless it actually has a dependency or is considered the cycle entry (due to its label) 042 int offset = (cycleEntry > 0 && nodes.get(0).getDependency() == null) ? 1 : 0; 043 Dependency[] dependencies = new Dependency[nodes.size() - offset + 1]; 044 for (int i = 0, n = dependencies.length - 1; i < n; i++) { 045 DependencyNode node = nodes.get(i + offset); 046 dependencies[i] = node.getDependency(); 047 // when cycle starts at root artifact as opposed to root dependency, synthesize a dependency 048 if (dependencies[i] == null) { 049 dependencies[i] = new Dependency(node.getArtifact(), null); 050 } 051 } 052 dependencies[dependencies.length - 1] = dependency; 053 this.dependencies = Collections.unmodifiableList(Arrays.asList(dependencies)); 054 this.cycleEntry = cycleEntry; 055 } 056 057 @Override 058 public List<Dependency> getPrecedingDependencies() { 059 return dependencies.subList(0, cycleEntry); 060 } 061 062 @Override 063 public List<Dependency> getCyclicDependencies() { 064 return dependencies.subList(cycleEntry, dependencies.size()); 065 } 066 067 /** 068 * Searches for a node associated with the given artifact. A version of the artifact is not considered during the 069 * search. 070 * 071 * @param nodes a list representing single path in the dependency graph. First element is the root. 072 * @param artifact to find among the parent nodes. 073 * @return the index of the node furthest from the root and associated with the given artifact, or {@literal -1} if 074 * there is no such node. 075 */ 076 public static int find(List<DependencyNode> nodes, Artifact artifact) { 077 078 for (int i = nodes.size() - 1; i >= 0; i--) { 079 DependencyNode node = nodes.get(i); 080 081 Artifact a = node.getArtifact(); 082 if (a == null) { 083 break; 084 } 085 086 if (!a.getArtifactId().equals(artifact.getArtifactId())) { 087 continue; 088 } 089 if (!a.getGroupId().equals(artifact.getGroupId())) { 090 continue; 091 } 092 if (!a.getExtension().equals(artifact.getExtension())) { 093 continue; 094 } 095 if (!a.getClassifier().equals(artifact.getClassifier())) { 096 continue; 097 } 098 /* 099 * NOTE: While a:1 and a:2 are technically different artifacts, we want to consider the path a:2 -> b:2 -> 100 * a:1 a cycle in the current context. The artifacts themselves might not form a cycle but their producing 101 * projects surely do. Furthermore, conflict resolution will always have to consider a:1 a loser (otherwise 102 * its ancestor a:2 would get pruned and so would a:1) so there is no point in building the sub graph of 103 * a:1. 104 */ 105 106 return i; 107 } 108 109 return -1; 110 } 111 112 @Override 113 public String toString() { 114 StringBuilder buffer = new StringBuilder(256); 115 int i = 0; 116 for (Dependency dependency : dependencies) { 117 if (i++ > 0) { 118 buffer.append(" -> "); 119 } 120 buffer.append(ArtifactIdUtils.toVersionlessId(dependency.getArtifact())); 121 } 122 return buffer.toString(); 123 } 124}