#!/usr/bin/env bash
# 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.
#
# This script was generated from java_stub_template.txt.  Please
# don't edit it directly.
#
# If present, these flags should either be at the beginning of the command
# line, or they should be wrapped in a --wrapper_script_flag=FLAG argument.
#
# --debug               Launch the JVM in remote debugging mode listening
# --debug=<port>        to the specified port or the port set in the
#                       DEFAULT_JVM_DEBUG_PORT environment variable (e.g.
#                       'export DEFAULT_JVM_DEBUG_PORT=8000') or else the
#                       default port of 5005.  The JVM starts suspended
#                       unless the DEFAULT_JVM_DEBUG_SUSPEND environment
#                       variable is set to 'n'.
# --main_advice=<class> Run an alternate main class with the usual main
#                       program and arguments appended as arguments.
# --main_advice_classpath=<classpath>
#                       Prepend additional class path entries.
# --jvm_flag=<flag>     Pass <flag> to the "java" command itself.
#                       <flag> may contain spaces. Can be used multiple times.
# --jvm_flags=<flags>   Pass space-separated flags to the "java" command
#                       itself. Can be used multiple times.
# --singlejar           Start the program from the packed-up deployment
#                       jar rather than from the classpath.
# --print_javabin       Print the location of java executable binary and exit.
# --classpath_limit=<length>
#                       Specify the maximum classpath length. If the classpath
#                       is shorter, this script passes it to Java as a command
#                       line flag, otherwise it creates a classpath jar.
#
# The remainder of the command line is passed to the program.

set -o posix

# Make it easy to insert 'set -x' or similar commands when debugging problems with this script.
eval "$JAVA_STUB_DEBUG"

# Prevent problems where the caller has exported CLASSPATH, causing our
# computed value to be copied into the environment and double-counted
# against the argv limit.
unset CLASSPATH

JVM_FLAGS_CMDLINE=()

# Processes an argument for the wrapper. Returns 0 if the given argument
# was recognized as an argument for this wrapper, and 1 if it was not.
function process_wrapper_argument() {
  case "$1" in
    --debug) JVM_DEBUG_PORT="${DEFAULT_JVM_DEBUG_PORT:-5005}" ;;
    --debug=*) JVM_DEBUG_PORT="${1#--debug=}" ;;
    --main_advice=*) MAIN_ADVICE="${1#--main_advice=}" ;;
    --main_advice_classpath=*) MAIN_ADVICE_CLASSPATH="${1#--main_advice_classpath=}" ;;
    --jvm_flag=*) JVM_FLAGS_CMDLINE+=( "${1#--jvm_flag=}" ) ;;
    --jvm_flags=*) JVM_FLAGS_CMDLINE+=( ${1#--jvm_flags=} ) ;;
    --singlejar) SINGLEJAR=1 ;;
    --print_javabin) PRINT_JAVABIN=1 ;;
    --classpath_limit=*)
        CLASSPATH_LIMIT="${1#--classpath_limit=}"
        echo "$CLASSPATH_LIMIT" | grep -q '^[0-9]\+$' || \
          die "ERROR: $self failed, --classpath_limit is not a number"
        ;;
    *)
      return 1 ;;
  esac
  return 0
}

die() {
  printf "%s: $1\n" "$0" "${@:2}" >&2
  exit 1
}

# Windows
function is_windows() {
  [[ "${OSTYPE}" =~ msys* ]] || [[ "${OSTYPE}" =~ cygwin* ]]
}

# macOS
function is_macos() {
  [[ "${OSTYPE}" =~ darwin* ]]
}

function available_utf8_locale() {
  # Both C.UTF-8 and en_US.UTF-8 do not cause any language-specific effects
  # when set as LC_CTYPE, but neither is certain to exist on all systems.
  #
  # https://github.com/bazelbuild/bazel/pull/17670: Note that the use of "env"
  # is important in these calls. Without "env", bash itself seems to pick up
  # the LC_CTYPE change as soon as the variable is defined and may emit a
  # warning when the locale files are not present. By using "env", bash never
  # sees the change and the 2>/dev/null redirection does the right thing.
  if [[ "$(env LC_CTYPE=C.UTF-8 locale charmap 2>/dev/null)" == "UTF-8" ]]; then
    echo "C.UTF-8"
  elif [[ "$(env LC_CTYPE=en_US.UTF-8 locale charmap 2>/dev/null)" == "UTF-8" ]]; then
    echo "en_US.UTF-8"
  fi
}

# Parse arguments sequentially until the first unrecognized arg is encountered.
# Scan the remaining args for --wrapper_script_flag=X options and process them.
ARGS=()
for ARG in "$@"; do
  if [[ "$ARG" == --wrapper_script_flag=* ]]; then
    process_wrapper_argument "${ARG#--wrapper_script_flag=}" \
      || die "invalid wrapper argument '%s'" "$ARG"
  elif [[ "${#ARGS}" -gt 0 ]] || ! process_wrapper_argument "$ARG"; then
    ARGS+=( "$ARG" )
  fi
done

# Find our runfiles tree.  We need this to construct the classpath
# (unless --singlejar was passed).
#
# Call this program X.  X was generated by a java_binary or java_test rule.
# X may be invoked in many ways:
#   1a) directly by a user, with $0 in the output tree
#   1b) via 'bazel run' (similar to case 1a)
#   2) directly by a user, with $0 in X's runfiles tree
#   3) by another program Y which has a data dependency on X, with $0 in Y's runfiles tree
#   4) via 'bazel test'
#   5) by a genrule cmd, with $0 in the output tree
#   6) case 3 in the context of a genrule
#
# For case 1, $0 will be a regular file, and the runfiles tree will be
# at $0.runfiles.
# For case 2, $0 will be a symlink to the file seen in case 1.
# For case 3, we use Y's runfiles tree, which will be a superset of X's.
# For case 4, $JAVA_RUNFILES and $TEST_SRCDIR should already be set.
# Case 5 is handled like case 1.
# Case 6 is handled like case 3.

# If we are running on Windows, convert the windows style path
# to unix style for detecting runfiles path.
if is_windows; then
  self=$(cygpath --unix "$0")
else
  self="$0"
fi

if [[ "$self" != /* ]]; then
  self="$PWD/$self"
fi

if [[ "$SINGLEJAR" != 1 || "%needs_runfiles%" == 1 ]]; then
  if [[ -z "$JAVA_RUNFILES" ]]; then
    while true; do
      if [[ -e "$self.runfiles" ]]; then
        JAVA_RUNFILES="$self.runfiles"
        break
      fi
      if [[ $self == *.runfiles/* ]]; then
        JAVA_RUNFILES="${self%.runfiles/*}.runfiles"
        break
      fi
      if [[ ! -L "$self" ]]; then
        break
      fi
      readlink="$(readlink "$self")"
      if [[ "$readlink" = /* ]]; then
        self="$readlink"
      else
        # resolve relative symlink
        self="${self%/*}/$readlink"
      fi
    done
    if [[ -n "$JAVA_RUNFILES" ]]; then
      export TEST_SRCDIR=${TEST_SRCDIR:-$JAVA_RUNFILES}
    elif [[ -f "${self}_deploy.jar" && "%needs_runfiles%" == 0 ]]; then
      SINGLEJAR=1;
    else
      die 'Cannot locate runfiles directory. (Set $JAVA_RUNFILES to inhibit searching.)'
    fi
  fi
fi

# If we are running on Windows, we need a windows style runfiles path for constructing CLASSPATH
if is_windows; then
  JAVA_RUNFILES=$(cygpath --windows "$JAVA_RUNFILES")
fi

export JAVA_RUNFILES
export RUNFILES_MANIFEST_FILE="${JAVA_RUNFILES}/MANIFEST"
export RUNFILES_MANIFEST_ONLY=%runfiles_manifest_only%

if [ -z "$RUNFILES_MANIFEST_ONLY" ]; then
  function rlocation() {
    if [[ "$1" = /* ]]; then
      echo $1
    else
      echo "$(dirname $RUNFILES_MANIFEST_FILE)/$1"
    fi
  }
else
  if ! is_macos; then
    # Read file into my_array
    oifs=$IFS
    IFS=$'\n'
    my_array=( $(sed -e 's/\r//g' "$RUNFILES_MANIFEST_FILE") )
    IFS=$oifs

    # Process each runfile line into a [key,value] entry in runfiles_array
    # declare -A is not supported on macOS because an old version of bash is used.
    declare -A runfiles_array
    for line in "${my_array[@]}"
    do
      line_split=($line)
      runfiles_array[${line_split[0]}]=${line_split[@]:1}
    done
  fi

  function rlocation() {
    if [[ "$1" = /* ]]; then
      echo $1
    else
      if is_macos; then
        # Print the rest of line after the first space
        # First, set the first column to empty and print rest of the line
        # Second, use a trick of awk to remove leading and trailing spaces.
        echo $(grep "^$1 " $RUNFILES_MANIFEST_FILE | awk '{ $1=""; print }' | awk '{ $1=$1; print }')
      else
        echo ${runfiles_array[$1]}
      fi
    fi
  }
fi

# Set JAVABIN to the path to the JVM launcher.
%javabin%

if [[ "$PRINT_JAVABIN" == 1 || "%java_start_class%" == "--print_javabin" ]]; then
  echo -n "$JAVABIN"
  exit 0
fi

if [[ "$SINGLEJAR" == 1 ]]; then
  CLASSPATH="${self}_deploy.jar"
  # Check for the deploy jar now.  If it doesn't exist, we can print a
  # more helpful error message than the JVM.
  [[ -r "$CLASSPATH" ]] \
    || die "Option --singlejar was passed, but %s does not exist.\n  (You may need to build it explicitly.)" "$CLASSPATH"
else
  # Create the shortest classpath we can, by making it relative if possible.
  RUNPATH="${JAVA_RUNFILES}/%workspace_prefix%"
  RUNPATH="${RUNPATH#$PWD/}"
  CLASSPATH=%classpath%
fi

# Export the locations which will be used to find the location of the classes from the classpath file.
export SELF_LOCATION="$self"
export CLASSLOADER_PREFIX_PATH="${RUNPATH}"

# If using Jacoco in offline instrumentation mode, the CLASSPATH contains instrumented files.
# We need to make the metadata jar with uninstrumented classes available for generating
# the lcov-compatible coverage report, and we don't want it on the classpath.
%set_jacoco_metadata%
%set_jacoco_main_class%
%set_jacoco_java_runfiles_root%
# export JACOCO_IS_JAR_WRAPPED for compatibility with older versions of
# JacocoCoverageRunner that check for this and not CLASSPATH_JAR
# TODO(cmita): Remove when this is no longer required
export JACOCO_IS_JAR_WRAPPED=0
export CLASSPATH_JAR=""

if [[ -n "$JVM_DEBUG_PORT" ]]; then
  JVM_DEBUG_SUSPEND=${DEFAULT_JVM_DEBUG_SUSPEND:-"y"}
  JVM_DEBUG_FLAGS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=${JVM_DEBUG_SUSPEND},address=${JVM_DEBUG_PORT}"
fi

if [[ -n "$MAIN_ADVICE_CLASSPATH" ]]; then
  CLASSPATH="${MAIN_ADVICE_CLASSPATH}:${CLASSPATH}"
fi

# Check if TEST_TMPDIR is available to use for scratch.
if [[ -n "$TEST_TMPDIR" && -d "$TEST_TMPDIR" ]]; then
  JVM_FLAGS+=" -Djava.io.tmpdir=$TEST_TMPDIR"
fi

ARGS=(
  ${JVM_DEBUG_FLAGS}
  ${JVM_FLAGS}
  %jvm_flags%
  "${JVM_FLAGS_CMDLINE[@]}"
  ${MAIN_ADVICE}
  %java_start_class%
  "${ARGS[@]}")


# Creates a JAR containing the classpath and put the result to stdout
function create_classpath_jar() {
  # Build class path.
  MANIFEST_CLASSPATH=()
  if is_windows; then
    CLASSPATH_SEPARATOR=";"
  else
    CLASSPATH_SEPARATOR=":"
  fi

  OLDIFS="$IFS"
  IFS="${CLASSPATH_SEPARATOR}" # Use a custom separator for the loop.
  current_dir=$(pwd)
  for path in ${CLASSPATH}; do
    # Loop through the characters of the path and convert characters that are
    # not alphanumeric nor -_.~/ to their 2-digit hexadecimal representation
    if [[ ! $path =~ ^[-_.~/a-zA-Z0-9]*$ ]]; then
      local i c buff
      local converted_path=""

      for ((i=0; i<${#path}; i++)); do
        c=${path:$i:1}
        case ${c} in
              [-_.~/a-zA-Z0-9] ) buff=${c} ;;
              * )               printf -v buff '%%%02x' "'$c'"
        esac
        converted_path+="${buff}"
      done
      path=${converted_path}
    fi

    if is_windows; then
      path="file:/${path}" # e.g. "file:/C:/temp/foo.jar"
    else
      # If not absolute, qualify the path
      case "${path}" in
        /*) ;; # Already an absolute path
        *) path="${current_dir}/${path}";; # Now qualified
      esac
      path="file:${path}" # e.g. "file:/usr/local/foo.jar"
    fi

    MANIFEST_CLASSPATH+=("${path}")
  done
  IFS="$OLDIFS"

  # Create manifest file
  MANIFEST_FILE="$(mktemp -t XXXXXXXX.jar_manifest)"
  (
    echo "Manifest-Version: 1.0"

    CLASSPATH_LINE="Class-Path: ${MANIFEST_CLASSPATH[*]}"
    CLASSPATH_MANIFEST_LINES=$(sed -E $'s/(.{71})/\\1\\\n /g' <<< "${CLASSPATH_LINE}")

    echo "$CLASSPATH_MANIFEST_LINES"
    echo "Created-By: Bazel"
  ) >$MANIFEST_FILE

  # Create classpath JAR file
  MANIFEST_JAR_FILE="$(mktemp -t XXXXXXXX-classpath.jar)"
  if is_windows; then
    MANIFEST_JAR_FILE="$(cygpath --windows "$MANIFEST_JAR_FILE")"
    MANIFEST_FILE="$(cygpath --windows "$MANIFEST_FILE")"
  fi
  if is_windows; then
    JARBIN="${JARBIN:=${JAVABIN%/java.exe}/jar.exe}"
  else
    JARBIN="${JARBIN:=${JAVABIN%/java}/jar}"
  fi
  $JARBIN cvfm "$MANIFEST_JAR_FILE" "$MANIFEST_FILE" >/dev/null || \
    die "ERROR: $self failed because $JARBIN failed"
  rm -f "$MANIFEST_FILE"

  echo "$MANIFEST_JAR_FILE"
}

# If the user didn't specify a --classpath_limit, use the default value.
if [ -z "$CLASSPATH_LIMIT" ]; then
  # Windows per-arg limit MAX_ARG_STRLEN == 8k
  # Linux per-arg limit MAX_ARG_STRLEN == 128k
  is_windows && CLASSPATH_LIMIT=7000 || CLASSPATH_LIMIT=120000
fi

# On non-macOS Unix, without any locale variable set, the JVM would use
# using ASCII rather than UTF-8 as the encoding for file system paths.
if ! is_macos; then
  if [ -z ${LC_CTYPE+x} ] && [ -z ${LC_ALL+x} ] && [ -z ${LANG+x} ]; then
    UTF8_LOCALE=$(available_utf8_locale)
    if [[ -n "$UTF8_LOCALE" ]]; then
      export LC_CTYPE="$UTF8_LOCALE"
    fi
  fi
fi

if (("${#CLASSPATH}" > ${CLASSPATH_LIMIT})); then
  # TODO(cmtia): Remove JACOCO_IS_JAR_WRAPPED when JacocoCoverageRunner will
  # never need it anymore.
  export JACOCO_IS_JAR_WRAPPED=1
  CLASSPATH_MANIFEST_JAR=$(create_classpath_jar)
  export CLASSPATH_JAR="$(basename $CLASSPATH_MANIFEST_JAR)"
  "$JAVABIN" -classpath "$CLASSPATH_MANIFEST_JAR" "${ARGS[@]}"
  exit_code=$?
  rm -f "$CLASSPATH_MANIFEST_JAR"
  exit $exit_code
else
  export JACOCO_IS_JAR_WRAPPED=0
  export CLASSPATH_JAR=""
  exec "$JAVABIN" -classpath $CLASSPATH "${ARGS[@]}"
fi
