/*
 * Copyright (c) 2011 Google, Inc.
 *
 * 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.common.truth;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.truth.StringUtil.format;

import javax.annotation.Nullable;

/*>>>import org.checkerframework.checker.nullness.compatqual.NullableType;*/
/** A parent type for some infrastructure used in the Verb. */
// TODO(cgruber) Remove the FailureMessageHolder inheritance and restructure to simplify verbs.
public abstract class AbstractVerb<T extends AbstractVerb<T>> extends FailureContext {
  private final FailureStrategy failureStrategy;

  public AbstractVerb(FailureStrategy failureStrategy) {
    this(failureStrategy, null);
  }

  public AbstractVerb(
      FailureStrategy failureStrategy, @Nullable String format, Object /*@NullableType*/... args) {
    super(format, args);
    this.failureStrategy = checkNotNull(failureStrategy);
  }

  protected FailureStrategy getFailureStrategy() {
    // TODO(cgruber): Extract this logic solely into the withFailureMessage() methods.
    return hasFailureMessage()
        ? new MessagePrependingFailureStrategy(failureStrategy, this)
        : failureStrategy;
  }

  /** Triggers the failure strategy with an empty failure message */
  public void fail() {
    getFailureStrategy().fail("");
  }

  /** Triggers the failure strategy with the given failure message */
  public void fail(@Nullable String format, Object /*@NullableType*/... args) {
    getFailureStrategy().fail(format(format, args));
  }

  /**
   * Overrides the failure message of the subsequent subject's propositions.
   *
   * @see com.google.common.truth.delegation.DelegationTest
   * @param failureMessage a descriptive message.
   * @return A custom verb which will show the descriptive message along with the normal failure
   *     text.
   */
  // TODO(cgruber) try to delete this (binary incompatible, but see if there's a way.
  public abstract T withFailureMessage(@Nullable String failureMessage);

  /**
   * Overrides the failure message of the subsequent subject's propositions.
   *
   * @see com.google.common.truth.delegation.DelegationTest
   * @param format a descriptive message with formatting template content.
   * @param args object parameters to be substituted into the message template.
   * @return A custom verb which will show the descriptive message along with the normal failure
   *     text.
   */
  public abstract T withFailureMessage(@Nullable String format, Object /*@NullableType*/... args);

  /**
   * The recommended method of extension of Truth to new types, which is documented in {@link
   * com.google.common.truth.delegation.DelegationTest}.
   *
   * @see com.google.common.truth.delegation.DelegationTest
   * @param factory a {@code SubjectFactory<S, T>} implementation
   * @return A custom verb for the type returned by the SubjectFactory
   */
  public <S extends Subject<S, D>, D, SF extends SubjectFactory<S, D>> DelegatedVerb<S, D> about(
      SF factory) {
    return new DelegatedVerb<S, D>(getFailureStrategy(), factory);
  }

  /**
   * A generic, advanced method of extension of Truth to new types, which is documented on {@link
   * DelegatedVerbFactory}. Extension creators should prefer {@link SubjectFactory} if possible.
   *
   * @param <V> the type of {@link AbstractDelegatedVerb} to return
   * @param factory a {@code DelegatedVerbFactory<V>} implementation
   * @return A custom verb of type {@code <V>}
   */
  public <V extends AbstractDelegatedVerb<V>> V about(DelegatedVerbFactory<V> factory) {
    return factory.createVerb(getFailureStrategy());
  }

  /** A special Verb implementation which wraps a SubjectFactory */
  public static final class DelegatedVerb<S extends Subject<S, T>, T>
      extends AbstractDelegatedVerb<DelegatedVerb<S, T>> {
    private final SubjectFactory<S, T> subjectFactory;

    private static class Factory<S extends Subject<S, T>, T>
        implements DelegatedVerbFactory<DelegatedVerb<S, T>> {
      private final SubjectFactory<S, T> subjectFactory;

      private Factory(SubjectFactory<S, T> subjectFactory) {
        this.subjectFactory = subjectFactory;
      }

      @Override
      public DelegatedVerb<S, T> createVerb(FailureStrategy failureStrategy) {
        return new DelegatedVerb<S, T>(failureStrategy, subjectFactory);
      }
    }

    public DelegatedVerb(FailureStrategy failureStrategy, SubjectFactory<S, T> subjectFactory) {
      super(failureStrategy, new Factory<S, T>(subjectFactory));
      this.subjectFactory = checkNotNull(subjectFactory);
    }

    public S that(@Nullable T target) {
      return subjectFactory.getSubject(failureStrategy, target);
    }
  }

  protected static class MessagePrependingFailureStrategy extends FailureStrategy {
    private final FailureStrategy delegate;
    private final FailureContext messageHolder;

    public MessagePrependingFailureStrategy(
        FailureStrategy delegate, FailureContext messageHolder) {
      this.delegate = checkNotNull(delegate);
      this.messageHolder = checkNotNull(messageHolder);
    }

    @Override
    public void fail(String message) {
      delegate.fail(prependFailureMessageIfAny(message));
    }

    @Override
    public void fail(String message, Throwable cause) {
      delegate.fail(prependFailureMessageIfAny(message), cause);
    }

    @Override
    public void failComparing(String message, CharSequence expected, CharSequence actual) {
      delegate.failComparing(prependFailureMessageIfAny(message), expected, actual);
    }

    private String prependFailureMessageIfAny(String message) {
      return messageHolder.getFailureMessage() == null
          ? message
          : messageHolder.getFailureMessage() + ": " + message;
    }
  }
}

@@ -3337,17 +3337,17 @@
 tory%3CS, 
-T
+D
 %3E%7D imple

Update documentation from a8ed5a172a35f7e817dadce8b9c393aceaa18512