// Copyright 2017 The Bazel Authors. 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.devtools.build.android.r8;

import javax.annotation.Nullable;

/**
 * Interface for collecting desugaring metadata that we can use to double-check correct desugaring
 * at the binary level by looking at the metadata written for all Jars on the runtime classpath
 * (b/65645388). Use {@link NoWriteCollectors} for "no-op" collectors and {@link
 * com.google.devtools.build.android.desugar.dependencies.MetadataCollector} for writing out
 * metadata files.
 */
// TODO(kmb): There could conceivably be a "self-contained" version where we check at the end that
// we actually saw all the companion classes (in recordDefaultMethods) that we "assumed"; useful
// for one-shot runs over an entire binary.
@SuppressWarnings("unused") // default implementations consist of empty method stubs
public interface DependencyCollector {

  /** Class name suffix used for interface companion classes. */
  String INTERFACE_COMPANION_SUFFIX = "$$CC";

  String D8_INTERFACE_COMPANION_SUFFIX = R8Utils.INTERFACE_COMPANION_SUFFIX;

  /**
   * Records that {@code origin} depends on companion class {@code target}. For the resulting binary
   * to be valid, {@code target} needs to exist, which isn't the case if the corresponding interface
   * is only available as a compile-time ("neverlink") dependency.
   */
  default void assumeCompanionClass(String origin, String target) {}

  /**
   * Records that {@code origin} transitively implements {@code target} but {@code target} isn't in
   * the classpath. This can lead to wrong desugarings if {@code target} or an interface it extends
   * defines default methods.
   */
  default void missingImplementedInterface(String origin, String target) {}

  /**
   * Records that the given interface extends the given interfaces.
   *
   * <p>This information is useful reference to double-check {@link #missingImplementedInterface}s
   * without reading and parsing .class files, specifically if default methods are defined in
   * interfaces that a missing interface transitively extends.
   */
  default void recordExtendedInterfaces(String origin, String... targets) {}

  /**
   * Records that the given interface has a companion class that includes the given number of
   * default methods (0 if there were only static methods). This method should not be called for
   * purely abstract interfaces, to allow verifying available companion classes against this.
   *
   * <p>This information is useful reference to double-check {@link #missingImplementedInterface}s
   * without reading and parsing .class files with better precision than just looking for companion
   * classes on the runtime classpath (which may only contain static methods).
   */
  default void recordDefaultMethods(String origin, int count) {}

  /**
   * Returns metadata to include into the desugaring output or {@code null} if none. Returning
   * anything but {@code null} will cause an extra file to be written into the output, including an
   * empty array.
   */
  @Nullable
  public byte[] toByteArray();

  /** Simple collectors that don't collect any information. */
  public enum NoWriteCollectors implements DependencyCollector {
    /** Singleton instance that does nothing. */
    NOOP,
    /**
     * Singleton instance that does nothing besides throwing if {@link #missingImplementedInterface}
     * is called.
     */
    FAIL_ON_MISSING {
      @Override
      public void missingImplementedInterface(String origin, String target) {
        throw new IllegalStateException(
            String.format(
                "Couldn't find interface %s on the classpath for desugaring %s", target, origin));
      }
    };

    @Override
    @Nullable
    public final byte[] toByteArray() {
      return null;
    }
  }
}
