// Copyright 2014 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.lib.rules.java;

import static com.google.devtools.build.lib.packages.BuildType.LABEL;
import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST;
import static com.google.devtools.build.lib.packages.ImplicitOutputsFunction.fromTemplates;

import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.LanguageDependentFragment.LibraryLanguage;
import com.google.devtools.build.lib.analysis.OutputGroupProvider;
import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.Runfiles;
import com.google.devtools.build.lib.analysis.Runfiles.Builder;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.packages.Attribute.LateBoundLabel;
import com.google.devtools.build.lib.packages.Attribute.LateBoundLabelList;
import com.google.devtools.build.lib.packages.AttributeMap;
import com.google.devtools.build.lib.packages.ImplicitOutputsFunction.SafeImplicitOutputsFunction;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.rules.java.DeployArchiveBuilder.Compression;
import com.google.devtools.build.lib.rules.java.JavaCompilationArgs.ClasspathType;
import com.google.devtools.build.lib.rules.java.JavaConfiguration.JavaOptimizationMode;
import com.google.devtools.build.lib.rules.java.proto.GeneratedExtensionRegistryProvider;
import com.google.devtools.build.lib.util.FileType;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.Collection;
import java.util.List;
import javax.annotation.Nullable;

/**
 * Pluggable Java compilation semantics.
 */
public interface JavaSemantics {

  LibraryLanguage LANGUAGE = new LibraryLanguage("Java");

  SafeImplicitOutputsFunction JAVA_LIBRARY_CLASS_JAR =
      fromTemplates("lib%{name}.jar");
  SafeImplicitOutputsFunction JAVA_LIBRARY_SOURCE_JAR =
      fromTemplates("lib%{name}-src.jar");

  SafeImplicitOutputsFunction JAVA_BINARY_CLASS_JAR =
      fromTemplates("%{name}.jar");
  SafeImplicitOutputsFunction JAVA_BINARY_SOURCE_JAR =
      fromTemplates("%{name}-src.jar");

  SafeImplicitOutputsFunction JAVA_BINARY_DEPLOY_JAR =
      fromTemplates("%{name}_deploy.jar");
  SafeImplicitOutputsFunction JAVA_BINARY_MERGED_JAR =
      fromTemplates("%{name}_merged.jar");
  SafeImplicitOutputsFunction JAVA_UNSTRIPPED_BINARY_DEPLOY_JAR =
      fromTemplates("%{name}_deploy.jar.unstripped");
  SafeImplicitOutputsFunction JAVA_BINARY_PROGUARD_MAP =
      fromTemplates("%{name}_proguard.map");
  SafeImplicitOutputsFunction JAVA_BINARY_PROGUARD_PROTO_MAP =
      fromTemplates("%{name}_proguard.pbmap");
  SafeImplicitOutputsFunction JAVA_BINARY_PROGUARD_SEEDS =
      fromTemplates("%{name}_proguard.seeds");
  SafeImplicitOutputsFunction JAVA_BINARY_PROGUARD_USAGE =
      fromTemplates("%{name}_proguard.usage");
  SafeImplicitOutputsFunction JAVA_BINARY_PROGUARD_CONFIG =
      fromTemplates("%{name}_proguard.config");

  SafeImplicitOutputsFunction JAVA_BINARY_DEPLOY_SOURCE_JAR =
      fromTemplates("%{name}_deploy-src.jar");

  SafeImplicitOutputsFunction JAVA_TEST_CLASSPATHS_FILE =
      fromTemplates("%{name}_classpaths_file");

  FileType JAVA_SOURCE = FileType.of(".java");
  FileType JAR = FileType.of(".jar");
  FileType PROPERTIES = FileType.of(".properties");
  FileType SOURCE_JAR = FileType.of(".srcjar");
  // TODO(bazel-team): Rename this metadata extension to something meaningful.
  FileType COVERAGE_METADATA = FileType.of(".em");

  /**
   * Label to the Java Toolchain rule. It is resolved from a label given in the java options.
   */
  String JAVA_TOOLCHAIN_LABEL = "//tools/defaults:java_toolchain";

  /** The java_toolchain.compatible_javacopts key for Java 7 javacopts */
  public static final String JAVA7_JAVACOPTS_KEY = "java7";
  /** The java_toolchain.compatible_javacopts key for Android javacopts */
  public static final String ANDROID_JAVACOPTS_KEY = "android";
  /** The java_toolchain.compatible_javacopts key for proto compilations. */
  public static final String PROTO_JAVACOPTS_KEY = "proto";

  LateBoundLabel<BuildConfiguration> JAVA_TOOLCHAIN =
      new LateBoundLabel<BuildConfiguration>(JAVA_TOOLCHAIN_LABEL, JavaConfiguration.class) {
        @Override
        public Label resolve(Rule rule, AttributeMap attributes,
            BuildConfiguration configuration) {
          return configuration.getFragment(JavaConfiguration.class).getToolchainLabel();
        }
      };

  /**
   * Name of the output group used for source jars.
   */
  String SOURCE_JARS_OUTPUT_GROUP =
      OutputGroupProvider.HIDDEN_OUTPUT_GROUP_PREFIX + "source_jars";

  /**
   * Name of the output group used for gen jars (the jars containing the class files for sources
   * generated from annotation processors).
   */
  String GENERATED_JARS_OUTPUT_GROUP =
      OutputGroupProvider.HIDDEN_OUTPUT_GROUP_PREFIX + "gen_jars";


  /**
   * Implementation for the :jvm attribute.
   */
  LateBoundLabel<BuildConfiguration> JVM =
      new LateBoundLabel<BuildConfiguration>(JavaImplicitAttributes.JDK_LABEL, Jvm.class) {
        @Override
        public Label resolve(Rule rule, AttributeMap attributes,
            BuildConfiguration configuration) {
          return configuration.getFragment(Jvm.class).getJvmLabel();
        }
      };

  /**
   * Implementation for the :host_jdk attribute.
   */
  LateBoundLabel<BuildConfiguration> HOST_JDK =
      new LateBoundLabel<BuildConfiguration>(JavaImplicitAttributes.JDK_LABEL, Jvm.class) {
        @Override
        public boolean useHostConfiguration() {
          return true;
        }

        @Override
        public Label resolve(Rule rule, AttributeMap attributes,
            BuildConfiguration configuration) {
          return configuration.getFragment(Jvm.class).getJvmLabel();
        }
      };

  /**
   * Implementation for the :java_launcher attribute. Note that the Java launcher is disabled by
   * default, so it returns null for the configuration-independent default value.
   */
  LateBoundLabel<BuildConfiguration> JAVA_LAUNCHER =
      new LateBoundLabel<BuildConfiguration>(JavaConfiguration.class) {
        @Override
        public Label resolve(Rule rule, AttributeMap attributes, BuildConfiguration configuration) {
          // don't read --java_launcher if this target overrides via a launcher attribute
          if (attributes != null && attributes.isAttributeValueExplicitlySpecified("launcher")) {
            return attributes.get("launcher", LABEL);
          }
          return configuration.getFragment(JavaConfiguration.class).getJavaLauncherLabel();
        }
      };

  LateBoundLabelList<BuildConfiguration> JAVA_PLUGINS =
      new LateBoundLabelList<BuildConfiguration>() {
        @Override
        public List<Label> resolve(Rule rule, AttributeMap attributes,
            BuildConfiguration configuration) {
          return ImmutableList.copyOf(configuration.getPlugins());
        }
      };

  /**
   * Implementation for the :proguard attribute.
   */
  LateBoundLabel<BuildConfiguration> PROGUARD =
      new LateBoundLabel<BuildConfiguration>(JavaConfiguration.class) {
        @Override
        public Label resolve(Rule rule, AttributeMap attributes,
            BuildConfiguration configuration) {
          return configuration.getFragment(JavaConfiguration.class).getProguardBinary();
        }
      };

  LateBoundLabelList<BuildConfiguration> EXTRA_PROGUARD_SPECS =
      new LateBoundLabelList<BuildConfiguration>() {
        @Override
        public List<Label> resolve(Rule rule, AttributeMap attributes,
            BuildConfiguration configuration) {
          return ImmutableList.copyOf(
              configuration.getFragment(JavaConfiguration.class).getExtraProguardSpecs());
        }
      };

  LateBoundLabelList<BuildConfiguration> BYTECODE_OPTIMIZERS =
      new LateBoundLabelList<BuildConfiguration>(JavaConfiguration.class) {
        @Override
        public List<Label> resolve(
            Rule rule, AttributeMap attributes, BuildConfiguration configuration) {
          // Use a modicum of smarts to avoid implicit dependencies where we don't need them.
          JavaOptimizationMode optMode =
              configuration.getFragment(JavaConfiguration.class).getJavaOptimizationMode();
          boolean hasProguardSpecs = attributes.has("proguard_specs")
              && !attributes.get("proguard_specs", LABEL_LIST).isEmpty();
          if (optMode == JavaOptimizationMode.NOOP
              || (optMode == JavaOptimizationMode.LEGACY && !hasProguardSpecs)) {
            return ImmutableList.<Label>of();
          }
          return ImmutableList.copyOf(
              Optional.presentInstances(
                  configuration
                      .getFragment(JavaConfiguration.class)
                      .getBytecodeOptimizers()
                      .values()));
        }
      };

  String IJAR_LABEL = "//tools/defaults:ijar";

  /**
   * Verifies if the rule contains any errors.
   *
   * <p>Errors should be signaled through {@link RuleContext}.
   */
  void checkRule(RuleContext ruleContext, JavaCommon javaCommon);

  /**
   * Verifies there are no conflicts in protos.
   *
   * <p>Errors should be signaled through {@link RuleContext}.
   */
  void checkForProtoLibraryAndJavaProtoLibraryOnSameProto(
      RuleContext ruleContext, JavaCommon javaCommon);

  void checkProtoDeps(RuleContext ruleContext, Collection<? extends TransitiveInfoCollection> deps);

  /**
   * Returns the main class of a Java binary.
   */
  String getMainClass(RuleContext ruleContext, ImmutableList<Artifact> srcsArtifacts);

  /**
   * Returns the primary class for a Java binary - either the main class, or, in case of a test,
   * the test class (not the test runner main class).
   */
  String getPrimaryClass(RuleContext ruleContext, ImmutableList<Artifact> srcsArtifacts);

  /**
   * Returns the resources contributed by a Java rule (usually the contents of the
   * {@code resources} attribute)
   */
  ImmutableList<Artifact> collectResources(RuleContext ruleContext);

  /**
   * Constructs the command line to call SingleJar to join all artifacts from
   * {@code classpath} (java code) and {@code resources} into {@code output}.
   */
  CustomCommandLine buildSingleJarCommandLine(BuildConfiguration configuration,
      Artifact output, String mainClass, ImmutableList<String> manifestLines,
      Iterable<Artifact> buildInfoFiles, ImmutableList<Artifact> resources,
      Iterable<Artifact> classpath, boolean includeBuildData,
      Compression compression, Artifact launcher);

  /**
   * Creates the action that writes the Java executable stub script.
   *
   * <p>Returns the launcher script artifact. This may or may not be the same as {@code executable},
   * depending on the implementation of this method. If they are the same, then this Artifact should
   * be used when creating both the {@code RunfilesProvider} and the {@code RunfilesSupport}. If
   * they are different, the new value should be used when creating the {@code RunfilesProvider} (so
   * it will be the stub script executed by "bazel run" for example), and the old value should be
   * used when creating the {@code RunfilesSupport} (so the runfiles directory will be named after
   * it).
   *
   * <p>For example on Windows we use a double dispatch approach: the launcher is a batch file (and
   * is created and returned by this method) which shells out to a shell script (the {@code
   * executable} argument).
   */
  Artifact createStubAction(
      RuleContext ruleContext,
      JavaCommon javaCommon,
      List<String> jvmFlags,
      Artifact executable,
      String javaStartClass,
      String javaExecutable);

  /**
   * Optionally creates a file containing the relative classpaths within the runfiles tree. If
   * {@link Optional#isPresent()}, then the caller should ensure the file appears in the runfiles.
   */
  Optional<Artifact> createClasspathsFile(RuleContext ruleContext, JavaCommon javaCommon)
      throws InterruptedException;

  /**
   * Adds extra runfiles for a {@code java_binary} rule.
   */
  void addRunfilesForBinary(RuleContext ruleContext, Artifact launcher,
      Runfiles.Builder runfilesBuilder);

  /**
   * Adds extra runfiles for a {@code java_library} rule.
   */
  void addRunfilesForLibrary(RuleContext ruleContext, Runfiles.Builder runfilesBuilder);

  /**
   * Returns the additional options to be passed to javac.
   */
  Iterable<String> getExtraJavacOpts(RuleContext ruleContext);

  /**
   * Add additional targets to be treated as direct dependencies.
   */
  void collectTargetsTreatedAsDeps(
      RuleContext ruleContext,
      ImmutableList.Builder<TransitiveInfoCollection> builder,
      ClasspathType type);

  /**
   * Enables coverage support for the java target - adds instrumented jar to the classpath and
   * modifies main class.
   *
   * @return new main class
   */
  String addCoverageSupport(
      JavaCompilationHelper helper,
      JavaTargetAttributes.Builder attributes,
      Artifact executable,
      Artifact instrumentationMetadata,
      JavaCompilationArtifacts.Builder javaArtifactsBuilder,
      String mainClass)
      throws InterruptedException;

  /**
   * Return the JVM flags to be used in a Java binary.
   */
  Iterable<String> getJvmFlags(
      RuleContext ruleContext, ImmutableList<Artifact> srcsArtifacts, List<String> userJvmFlags);

  /**
   * Adds extra providers to a Java target.
   * @throws InterruptedException
   */
  void addProviders(RuleContext ruleContext,
      JavaCommon javaCommon,
      List<String> jvmFlags,
      Artifact classJar,
      Artifact srcJar,
      Artifact genJar,
      Artifact gensrcJar,
      ImmutableMap<Artifact, Artifact> compilationToRuntimeJarMap,
      NestedSetBuilder<Artifact> filesBuilder,
      RuleConfiguredTargetBuilder ruleBuilder) throws InterruptedException;

  /**
   * Translates XMB messages to translations artifact suitable for Java targets.
   */
  ImmutableList<Artifact> translate(RuleContext ruleContext, JavaConfiguration javaConfig,
      List<Artifact> messages);

  /**
   * Get the launcher artifact for a java binary, creating the necessary actions for it.
   *
   * @param ruleContext The rule context
   * @param common The common helper class.
   * @param deployArchiveBuilder the builder to construct the deploy archive action (mutable).
   * @param runfilesBuilder the builder to construct the list of runfiles (mutable).
   * @param jvmFlags the list of flags to pass to the JVM when running the Java binary (mutable).
   * @param attributesBuilder the builder to construct the list of attributes of this target
   *        (mutable).
   * @return the launcher as an artifact
   * @throws InterruptedException
   */
  Artifact getLauncher(final RuleContext ruleContext, final JavaCommon common,
      DeployArchiveBuilder deployArchiveBuilder, Runfiles.Builder runfilesBuilder,
      List<String> jvmFlags, JavaTargetAttributes.Builder attributesBuilder, boolean shouldStrip)
      throws InterruptedException;

  /**
   * Add extra dependencies for runfiles of a Java binary.
   */
  void addDependenciesForRunfiles(RuleContext ruleContext, Builder builder);

  /**
   * Add a source artifact to a {@link JavaTargetAttributes.Builder}. It is called when a source
   * artifact is processed but is not matched by default patterns in the
   * {@link JavaTargetAttributes.Builder#addSourceArtifacts(Iterable)} method. The semantics can
   * then detect its custom artifact types and add it to the builder.
   */
  void addArtifactToJavaTargetAttribute(JavaTargetAttributes.Builder builder, Artifact srcArtifact);

  /**
   * Works on the list of dependencies of a java target to builder the {@link JavaTargetAttributes}.
   * This work is performed in {@link JavaCommon} for all java targets.
   */
  void commonDependencyProcessing(RuleContext ruleContext, JavaTargetAttributes.Builder attributes,
      Collection<? extends TransitiveInfoCollection> deps);

  /**
   * Takes the path of a Java resource and tries to determine the Java
   * root relative path of the resource.
   *
   * <p>This is only used if the Java rule doesn't have a {@code resource_strip_prefix} attribute.
   *
   * @param path the root relative path of the resource.
   * @return the Java root relative path of the resource of the root
   *         relative path of the resource if no Java root relative path can be
   *         determined.
   */
  PathFragment getDefaultJavaResourcePath(PathFragment path);

  /**
   * @return a list of extra arguments to appends to the runfiles support.
   */
  List<String> getExtraArguments(RuleContext ruleContext, ImmutableList<Artifact> sources);

  /**
   * @return main class (entry point) for the Java compiler.
   */
  String getJavaBuilderMainClass();

  /**
   * @return An artifact representing the protobuf-format version of the
   * proguard mapping, or null if the proguard version doesn't support this.
   */
  Artifact getProtoMapping(RuleContext ruleContext) throws InterruptedException;

  /**
   * Produces the proto generated extension registry artifacts, or <tt>null</tt>
   * if no registry needs to be generated for the provided <tt>ruleContext</tt>.
   */
  @Nullable
  GeneratedExtensionRegistryProvider createGeneratedExtensionRegistry(
      RuleContext ruleContext,
      JavaCommon common,
      NestedSetBuilder<Artifact> filesBuilder,
      JavaCompilationArtifacts.Builder javaCompilationArtifactsBuilder,
      JavaRuleOutputJarsProvider.Builder javaRuleOutputJarsProviderBuilder,
      JavaSourceJarsProvider.Builder javaSourceJarsProviderBuilder)
      throws InterruptedException;

  Artifact getObfuscatedConstantStringMap(RuleContext ruleContext) throws InterruptedException;
}
