/*
 * Copyright 2019 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.collect.ImmutableSetMultimap;
import java.util.Objects;
import javax.annotation.Nullable;

/**
 * A missing or incompatible symbol. This constitutes the cause of a linkage error (without the
 * source class).
 *
 * @see <a
 *     href="https://github.com/GoogleCloudPlatform/cloud-opensource-java/blob/master/library-best-practices/glossary.md#linkage-error">
 *     Java Dependency Glossary: Linkage Error</a>
 */
public final class SymbolProblem {

  private final ErrorType errorType;
  private final Symbol symbol;
  private final ClassFile containingClass;

  public SymbolProblem(Symbol symbol, ErrorType errorType, @Nullable ClassFile containingClass) {
    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.errorType = checkNotNull(errorType);
    this.containingClass = containingClass;
  }

  /** Returns the errorType why the symbol was not resolved. */
  ErrorType getErrorType() {
    return errorType;
  }

  /** Returns the target symbol that was not resolved. */
  Symbol getSymbol() {
    return symbol;
  }

  /**
   * Returns the referenced class that contains the symbol. Null when the target class is not found
   * in the class path (this is the case if the errorType is {@code CLASS_NOT_FOUND} for top-level
   * classes).
   *
   * <p>In case of a nested class is missing while its outer class is found in the class path, this
   * method returns the outer class.
   */
  @Nullable
  public ClassFile getContainingClass() {
    return containingClass;
  }

  @Override
  public boolean equals(Object other) {
    if (this == other) {
      return true;
    }
    if (other == null || getClass() != other.getClass()) {
      return false;
    }
    SymbolProblem that = (SymbolProblem) other;
    return errorType == that.errorType
        && symbol.equals(that.symbol)
        && Objects.equals(containingClass, that.containingClass);
  }

  @Override
  public int hashCode() {
    return Objects.hash(errorType, symbol, containingClass);
  }

  @Override
  public final String toString() {
    String jarInfo =
        containingClass != null
            ? String.format("(%s) ", containingClass.getJar().getFileName())
            : "";
    return jarInfo + getErrorType().getMessage(symbol.toString());
  }

  public static String formatSymbolProblems(
      ImmutableSetMultimap<SymbolProblem, ClassFile> symbolProblems) {
    StringBuilder output = new StringBuilder();

    symbolProblems
        .asMap()
        .forEach(
            (problem, classFiles) -> {
              int referenceCount = classFiles.size();
              output.append(
                  String.format(
                      "%s;\n  referenced by %d class file%s\n",
                      problem, referenceCount, referenceCount > 1 ? "s" : ""));
              classFiles.forEach(
                  classFile -> {
                    output.append("    " + classFile.getBinaryName());
                    output.append(" (" + classFile.getJar().getFileName() + ")\n");
                  });
            });

    return output.toString();
  }
}

@@ -650,22 +650,71 @@
 %0Aimport 
-static
+com.google.common.annotations.VisibleForTesting;%0Aimport
  com.goo
@@ -746,23 +746,9 @@
 ions
-.checkNotNull;%0A
+;
 %0Aimp
@@ -903,39 +903,20 @@
 mbol
-. This constitutes
  th
-e
+at
  cause
- of
+s
  a l
@@ -931,38 +931,8 @@
 rror
- (without the%0A * source class)
 .%0A *
@@ -942,23 +942,16 @@
  @see %3Ca
-%0A *    
  href=%22h
@@ -961,95 +961,16 @@
 s://
-github.com/GoogleCloudPlatform/cloud-opensource-java/blob/master/library-best-practices
+jlbp.dev
 /glo
@@ -979,10 +979,12 @@
 ary.
-md
+html
 #lin
@@ -1199,16 +1199,37 @@
 Class;%0A%0A
+  @VisibleForTesting%0A
   public
@@ -1322,16 +1322,30 @@
 ) %7B%0A    
+Preconditions.
 checkNot
@@ -1595,16 +1595,30 @@
 rType = 
+Preconditions.
 checkNot
@@ -1935,37 +1935,41 @@
 the 
-referenced class that
+class that is expected to
  contain
 s th
@@ -1964,17 +1964,16 @@
  contain
-s
  the sym
@@ -1981,72 +1981,56 @@
 ol. 
-Null when the target class is not found%0A   * in the class path (
+If the symbol is a method%0A   * or a field, then 
 this
@@ -2042,225 +2042,215 @@
 he c
+l
 as
-e if the errorType is %7B@code CLASS_NOT_FOUND%7D for top-level%0A   * classes).%0A   *%0A   * %3Cp%3EIn case of a nested
+s where the symbol was expected to be found.%0A   * If the symbol is an inner
  class
- 
+, th
 is 
-m
 is
-sing while its outer class is found in the class path, this%0A   * method returns the outer class
+ the outer class that was expected %0A   * to contain the inner class. If the symbol is an outer class, this is null
 .%0A  

Simplify javadoc (#1140)