/*
 * Copyright 2012 Google Inc. All Rights Reserved.
 *
 * 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.errorprone.matchers;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;

import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.fixes.Fix;
import com.sun.source.tree.Tree;
import java.util.List;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nullable;

/**
 * Simple data object containing the information captured about an AST match.
 * Can be printed in a UI, or output in structured format for use by tools.
 * @author alexeagle@google.com (Alex Eagle)
 */
public class Description {
  /** Describes the sentinel value of the case where the match failed. */
  public static final Description NO_MATCH =
      new Description(
          null, "<no match>", "<no match>", "<no match>", ImmutableList.<Fix>of(), SUGGESTION);

  private static final String UNDEFINED_CHECK_NAME = "Undefined";

  /**
   * The AST node that matched.
   */
  public final Tree node;

  /**
   * The name of the check that produced the match.
   */
  public final String checkName;

  /**
   * The raw message, not including the check name or the link.
   */
  private final String rawMessage;

  /** The raw link URL for the check. May be null if there is no link. */
  @Nullable private final String linkUrl;
  /**
   * A list of fixes to suggest in an error message or use in automated refactoring.  Fixes are
   * in order of decreasing preference, from most preferred to least preferred.
   */
  public final ImmutableList<Fix> fixes;

  /**
   * Is this a warning, error, etc.?
   */
  public final BugPattern.SeverityLevel severity;

  /**
   * Returns the message to be printed by the compiler when a match is found in interactive use.
   * Includes the name of the check and a link for more information.
   */
  public String getMessage() {
    return String.format("[%s] %s", checkName, getMessageWithoutCheckName());
  }

  /** Returns a link associated with this finding or null if there is no link. */
  @Nullable
  public String getLink() {
    return linkUrl;
  }

  /** Returns the raw message, not including a link or check name. */
  public String getRawMessage() {
    return rawMessage;
  }

  /**
   * Returns the message, not including the check name but including the link.
   */
  public String getMessageWithoutCheckName() {
    return linkUrl != null
        ? String.format("%s\n%s", rawMessage, linkTextForDiagnostic(linkUrl))
        : String.format("%s", rawMessage);
  }

  /** TODO(cushon): Remove this constructor and ensure that there's always a check name. */
  @Deprecated
  public Description(
      Tree node, String message, Fix suggestedFix, BugPattern.SeverityLevel severity) {
    this(node, UNDEFINED_CHECK_NAME, message, message, ImmutableList.of(suggestedFix), severity);
    if (suggestedFix == null) {
      throw new IllegalArgumentException("suggestedFix must not be null.");
    }
  }

  private Description(
      Tree node,
      String checkName,
      String rawMessage,
      String linkUrl,
      ImmutableList<Fix> fixes,
      SeverityLevel severity) {
    this.node = node;
    this.checkName = checkName;
    this.rawMessage = rawMessage;
    this.linkUrl = linkUrl;
    this.fixes = fixes;
    this.severity = severity;
  }

  @CheckReturnValue
  public Description applySeverityOverride(SeverityLevel severity) {
    return new Description(node, checkName, rawMessage, linkUrl, fixes, severity);
  }

  @CheckReturnValue
  public Description filterFixes(Predicate<? super Fix> predicate) {
    return new Description(
        node,
        checkName,
        rawMessage,
        linkUrl,
        ImmutableList.copyOf(Iterables.filter(fixes, predicate)),
        severity);
  }

  /**
   * Construct the link text to include in the compiler error message. Returns null if there is
   * no link.
   */
  private static String linkTextForDiagnostic(String linkUrl) {
    return isNullOrEmpty(linkUrl) ? null : "  (see " + linkUrl + ")";
  }

  /**
   * Returns a new builder for {@link Description}s.
   */
  public static Builder builder(
      Tree node, String name, @Nullable String link, SeverityLevel severity, String message) {
    return new Builder(node, name, link, severity, message);
  }

  /**
   * Builder for {@code Description}s.
   */
  public static class Builder {
    private final Tree node;
    private final String name;
    private final String linkUrl;
    private final SeverityLevel severity;
    private final ImmutableList.Builder<Fix> fixListBuilder = ImmutableList.builder();
    private String rawMessage;

    private Builder(
        Tree node,
        String name,
        @Nullable String linkUrl,
        SeverityLevel severity,
        String rawMessage) {
      this.node = Preconditions.checkNotNull(node);
      this.name = Preconditions.checkNotNull(name);
      this.linkUrl = linkUrl;
      this.severity = Preconditions.checkNotNull(severity);
      this.rawMessage = Preconditions.checkNotNull(rawMessage);
    }

    /**
     * Adds a suggested fix for this {@code Description}. Fixes should be added in order of
     * decreasing preference. Adding an empty fix is a no-op.
     *
     * @param fix a suggested fix for this problem
     * @throws IllegalArgumentException if {@code fix} is {@code null}
     */
    public Builder addFix(Fix fix) {
      checkArgument(fix != null, "fix must not be null");
      if (!fix.isEmpty()) {
        fixListBuilder.add(fix);
      }
      return this;
    }

    /**
     * Add each fix in order.
     *
     * @param fixes a list of suggested fixes for this problem
     * @throws IllegalArgumentException if {@code fixes} or any of its elements are {@code null}
     */
    public Builder addAllFixes(List<? extends Fix> fixes) {
      checkArgument(fixes != null, "fixes must not be null");
      for (Fix fix : fixes) {
        addFix(fix);
      }
      return this;
    }
    
    /**
     * Set a custom error message for this {@code Description}.  The custom message will be used
     * instead of the summary field as the text for the diagnostic message.
     *
     * @param message A custom error message without the check name ("[checkname]") or link
     */
    public Builder setMessage(String message) {
      if (message == null) {
        throw new IllegalArgumentException("message must not be null");
      }
      this.rawMessage = message;
      return this;
    }

    public Description build() {
      return new Description(node, name, rawMessage, linkUrl, fixListBuilder.build(), severity);
    }
  }

}

@@ -4206,24 +4206,111 @@
 erity;%0A  %7D%0A%0A
+  /** Internal-only. Has no effect if applied to a Description within a BugChecker. */%0A
   @CheckRetu

Add comment to applySeverityOverride clarifying it doesn't do anything from a BugChecker.