
com.google.cloud.tools.opensource.classpath.LinkageProblem Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dependencies Show documentation
Show all versions of dependencies Show documentation
A library for analyzing Maven artifact dependency graphs
The newest version!
/*
* Copyright 2020 Google LLC.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.cloud.tools.opensource.classpath;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.cloud.tools.opensource.dependencies.Artifacts;
import com.google.cloud.tools.opensource.dependencies.DependencyPath;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimaps;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.annotation.Nullable;
import org.eclipse.aether.artifact.Artifact;
/**
* A linkage error describing an invalid reference from {@code sourceClass} to {@code symbol}.
*
* @see Java Dependency Glossary: Linkage
* Error
*/
public abstract class LinkageProblem {
private final Symbol symbol;
private final ClassFile sourceClass;
private final String symbolProblemMessage;
private LinkageProblemCause cause;
private ClassFile targetClass;
/**
* A linkage error describing an invalid reference.
*
* @param symbolProblemMessage human-friendly description of this linkage error. Used in
* conjunction with {@code symbol}, this value explains why we consider the reference to
* {@code symbol} as a linkage error.
* @param sourceClass the source of the invalid reference.
* @param symbol the target of the invalid reference
* @param targetClass the class file that contains the symbol. {@code Null} when the class does
* not exist in the class path.
*/
LinkageProblem(
String symbolProblemMessage,
ClassFile sourceClass,
Symbol symbol,
@Nullable ClassFile targetClass) {
this.symbolProblemMessage = Preconditions.checkNotNull(symbolProblemMessage);
Preconditions.checkNotNull(symbol);
// After finding symbol problem, there is no need to have SuperClassSymbol over ClassSymbol.
this.symbol =
symbol instanceof SuperClassSymbol ? new ClassSymbol(symbol.getClassBinaryName()) : symbol;
this.sourceClass = Preconditions.checkNotNull(sourceClass);
this.targetClass = targetClass;
}
/** Returns the symbol that was not resolved. */
public Symbol getSymbol() {
return symbol;
}
/** Returns the class that contains a symbolic reference to a symbol that is not available. */
public ClassFile getSourceClass() {
return sourceClass;
}
/**
* Returns the class that is expected to contain the symbol. If the symbol is a method or a field,
* then this is the class where the symbol was expected to be found. If the symbol is an inner
* class, this is the outer class that was expected to contain the inner class. If the symbol is
* an outer class that is unknown or missing, this is null.
*/
@Nullable
public ClassFile getTargetClass() {
return targetClass;
}
void setCause(LinkageProblemCause cause) {
this.cause = checkNotNull(cause);
}
LinkageProblemCause getCause() {
return cause;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
LinkageProblem that = (LinkageProblem) other;
return symbol.equals(that.symbol) && Objects.equals(sourceClass, that.sourceClass)
&& Objects.equals(targetClass, that.targetClass);
}
@Override
public int hashCode() {
return Objects.hash(symbol, sourceClass, targetClass);
}
@Override
public String toString() {
return formatSymbolProblem() + " referenced by " + sourceClass;
}
/**
* Returns the description of the problem on the {@code symbol}. This description does not include
* the {@code sourceClass}. This value is useful when grouping {@link LinkageProblem}s by their
* {@code symbol}s.
*/
public String formatSymbolProblem() {
String result = symbol + " " + symbolProblemMessage;
if (targetClass != null) {
String jarInfo = "(" + getTargetClass().getClassPathEntry() + ") ";
result = jarInfo + result;
}
return result;
}
protected String formatSymbolProblemWithReferenceCount(int referenceCount) {
return String.format(
"%s;\n referenced by %d class file%s\n",
this.formatSymbolProblem(), referenceCount, referenceCount > 1 ? "s" : "");
}
/** Returns mapping from symbol problem description to the names of the source classes. */
public static ImmutableMap> groupBySymbolProblem(
Iterable linkageProblems) {
ImmutableListMultimap groupedMultimap =
Multimaps.index(linkageProblems, problem -> problem.formatSymbolProblem());
ListMultimap symbolProblemToSourceClasses =
Multimaps.transformValues(
groupedMultimap, problem -> problem.getSourceClass().getBinaryName());
Map> valueTransformed =
Maps.transformValues(symbolProblemToSourceClasses.asMap(), ImmutableSet::copyOf);
return ImmutableMap.copyOf(valueTransformed);
}
/**
* Returns the formatted {@code linkageProblems} by grouping them by the {@code symbol}s. If
* {@code classPathResult} is not null, it supplies dependency paths from the root to the
* artifacts in the problems.
*/
public static String formatLinkageProblems(
Set linkageProblems, @Nullable ClassPathResult classPathResult) {
StringBuilder output = new StringBuilder();
// Don't group AbstractMethodProblems by symbols because they do not fit in the
// "... referenced by ..." format.
ImmutableSet.Builder abstractMethodProblems = ImmutableSet.builder();
ImmutableSet.Builder problemsToGroupBySymbols = ImmutableSet.builder();
for (LinkageProblem linkageProblem : linkageProblems) {
if (linkageProblem instanceof AbstractMethodProblem) {
abstractMethodProblems.add((AbstractMethodProblem) linkageProblem);
} else {
problemsToGroupBySymbols.add(linkageProblem);
}
}
// Group by the symbols
ImmutableListMultimap groupBySymbols =
Multimaps.index(problemsToGroupBySymbols.build(), problem -> problem.getSymbol());
groupBySymbols
.asMap()
.forEach(
(symbol, problems) -> {
// problems all have the same symbol problem
LinkageProblem firstProblem = Iterables.getFirst(problems, null);
int referenceCount = problems.size();
output.append(firstProblem.formatSymbolProblemWithReferenceCount(referenceCount));
ImmutableSet.Builder causesBuilder = ImmutableSet.builder();
problems.forEach(
problem -> {
ClassFile sourceClassFile = problem.getSourceClass();
output.append(" " + sourceClassFile.getBinaryName());
output.append(" (" + sourceClassFile.getClassPathEntry() + ")\n");
LinkageProblemCause cause = problem.getCause();
if (cause != null) {
causesBuilder.add(cause);
}
});
ImmutableSet causes = causesBuilder.build();
if (!causes.isEmpty()) {
output.append(" Cause:\n");
for (LinkageProblemCause cause : causes) {
String causeWithIndent = cause.toString().replaceAll("\n", "\n ");
output.append(" " + causeWithIndent + "\n");
}
}
});
for (AbstractMethodProblem abstractMethodProblem : abstractMethodProblems.build()) {
output.append(abstractMethodProblem + "\n");
LinkageProblemCause cause = abstractMethodProblem.getCause();
if (cause != null) {
output.append(" Cause:\n");
String causeWithIndent = cause.toString().replaceAll("\n", "\n ");
output.append(" " + causeWithIndent + "\n");
}
}
if (classPathResult != null) {
String dependencyPaths = dependencyPathsOfProblematicJars(classPathResult, linkageProblems);
output.append(dependencyPaths);
}
return output.toString();
}
private static String dependencyPathsOfProblematicJars(
ClassPathResult classPathResult, Set linkageProblems) {
ImmutableSet.Builder problematicJars = ImmutableSet.builder();
for (LinkageProblem problem : linkageProblems) {
if (problem.getTargetClass() != null) {
problematicJars.add(problem.getTargetClass().getClassPathEntry());
}
ClassFile sourceClass = problem.getSourceClass();
problematicJars.add(sourceClass.getClassPathEntry());
}
return "Problematic artifacts in the dependency tree:\n"
+ classPathResult.formatDependencyPaths(problematicJars.build());
}
String describe(DependencyConflict conflict) {
DependencyPath pathToSelectedArtifact = conflict.getPathToSelectedArtifact();
Artifact selected = pathToSelectedArtifact.getLeaf();
String selectedCoordinates = Artifacts.toCoordinates(selected);
DependencyPath pathToArtifactThruSource = conflict.getPathToArtifactThruSource();
Artifact unselected = pathToArtifactThruSource.getLeaf();
String unselectedCoordinates = Artifacts.toCoordinates(unselected);
return "Dependency conflict: "
+ selectedCoordinates
+ " does not define "
+ getSymbol()
+ " but "
+ unselectedCoordinates
+ " defines it.\n"
+ " selected: "
+ pathToSelectedArtifact
+ "\n unselected: "
+ pathToArtifactThruSource;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy