All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.cloud.tools.opensource.classpath.LinkageProblem Maven / Gradle / Ivy

/*
 * 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.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;

/**
 * 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
   */
  LinkageProblem(String symbolProblemMessage, ClassFile sourceClass, Symbol symbol, 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 target symbol that was not resolved. */
  public Symbol getSymbol() {
    return symbol;
  }

  /** Returns the source of the invalid reference which this linkage error represents. */
  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 target class
   * 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 final 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;
  }

  /** 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. */
  public static String formatLinkageProblems(Set linkageProblems) {
    StringBuilder output = new StringBuilder();

    // Group by the symbols
    ImmutableListMultimap groupBySymbols =
        Multimaps.index(linkageProblems, 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(
                  String.format(
                      "%s;\n  referenced by %d class file%s\n",
                      firstProblem.formatSymbolProblem(),
                      referenceCount,
                      referenceCount > 1 ? "s" : ""));
              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");
                }
              }
            });

    return output.toString();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy