Add script to merge upstream package

Test: ojluni_merge_package java.util.concurrent.atomic
Test: ojluni_cleanup_merge_branches
Change-Id: I2dfdd53b389113b71c8f8a240bbeb8e791e3ccfa
diff --git a/tools/expected_upstream/install_tools.sh b/tools/expected_upstream/install_tools.sh
index 3763066..604aedd 100755
--- a/tools/expected_upstream/install_tools.sh
+++ b/tools/expected_upstream/install_tools.sh
@@ -5,12 +5,14 @@
 # See go/pip-install-remediation how to regenerate the requirements.txt file.
 pip3 install --require-hashes -r ${THIS_DIR}/requirements.txt
 
+pushd ${THIS_DIR}
 git fetch aosp expected_upstream
 git fetch aosp upstream-openjdk7u
 git fetch aosp upstream-openjdk8u
 git fetch aosp upstream-openjdk9
 git fetch aosp upstream-openjdk11u
 git fetch aosp upstream-openjdk17u
+popd
 
 alias ojluni_refresh_files=${THIS_DIR}/ojluni_refresh_files.py
 alias ojluni_merge_to_master=${THIS_DIR}/ojluni_merge_to_master.py
@@ -18,7 +20,8 @@
 alias ojluni_run_tool_tests='PYTHONPATH=${PYTHONPATH}:${THIS_DIR} python3 -B -m unittest discover -v -s tests -p "*_test.py"'
 alias ojluni_upgrade_identicals=${THIS_DIR}/ojluni_upgrade_identicals.py
 alias ojluni_versions_report=${THIS_DIR}/ojluni_versions_report.py
-
+alias ojluni_merge_package=${THIS_DIR}/ojluni_merge_package.sh
+alias ojluni_cleanup_merged_branches=${THIS_DIR}/ojluni_cleanup_merged_branches.sh
 
 _ojluni_modify_expectation ()
 {
diff --git a/tools/expected_upstream/ojluni_cleanup_merged_branches.sh b/tools/expected_upstream/ojluni_cleanup_merged_branches.sh
new file mode 100755
index 0000000..2037ee7
--- /dev/null
+++ b/tools/expected_upstream/ojluni_cleanup_merged_branches.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# Script that cleans up branches created by ojluni_merge_to_master. They are
+# usually name something like 'expected_upstream_e0bywxovb5'.
+
+if [[ -z "${ANDROID_BUILD_TOP}" ]]
+then
+  echo -e "ANDROID_BUILD_TOP not found. You need to run lunch first."
+  exit 1
+fi
+
+pushd "${ANDROID_BUILD_TOP}/libcore"
+
+for br in $(git branch | grep -E "^\s*expected_upstream_[a-z0-9]+$")
+do
+  echo git branch -D ${br}
+  git branch -D ${br}
+done
+
+popd
diff --git a/tools/expected_upstream/ojluni_merge_package.sh b/tools/expected_upstream/ojluni_merge_package.sh
new file mode 100755
index 0000000..8b612fa
--- /dev/null
+++ b/tools/expected_upstream/ojluni_merge_package.sh
@@ -0,0 +1,189 @@
+#!/bin/bash
+
+SELF=$(basename "${0}")
+DEFAULT_TAG="jdk17u/jdk-17.0.5-ga"
+SUPPORTED_TAGS="jdk7u/jdk7u40-b60"
+SUPPORTED_TAGS="${SUPPORTED_TAGS} jdk8u/jdk8u121-b13"
+SUPPORTED_TAGS="${SUPPORTED_TAGS} jdk8u/jdk8u60-b31"
+SUPPORTED_TAGS="${SUPPORTED_TAGS} jdk9/jdk-9+181"
+SUPPORTED_TAGS="${SUPPORTED_TAGS} jdk11u/jdk-11+28"
+SUPPORTED_TAGS="${SUPPORTED_TAGS} jdk11u/jdk-11.0.13-ga"
+SUPPORTED_TAGS="${SUPPORTED_TAGS} jdk17u/jdk-17.0.2-ga"
+SUPPORTED_TAGS="${SUPPORTED_TAGS} jdk17u/jdk-17.0.5-ga"
+
+USAGE=$(cat << EndOfUsage
+Usage:
+  ${SELF} [-b <bug_number>] [-t <upstream_tag>] <package_name> <package_name> ...
+  For example:
+    ${SELF} -b 123456 -t jdk17u/jdk-17.0.5.-ga java.util.concurrent java.util.concurrent.atomic
+
+Possible arguments:
+  -h|--help - print help and exit
+  -t|--tag - the upstream tag to merge to; default: ${DEFAULT_TAG} or
+             OJLUNI_MERGE_TARGET (if defined)
+  -b|--bug - the bug number to use in the commit message; if not defined it will
+             be picked up from the libcore branch name (for example
+             "b-12345-oj-merge" -> "-b 12345")
+
+The supported upstream tags are:
+  $(echo ${SUPPORTED_TAGS} | sed 's/ /\n  /g')
+EndOfUsage
+)
+
+HELP=$(cat << EndOfHelp
+Merges one or more packages from an upstream branch.
+
+This will use the correct form of add/modify based on what is already stored in
+libcore/EXPECTED_UPSTREAM.  Also it will find new files in the new version of
+the upstream package and add those as well.
+
+${USAGE}
+EndOfHelp
+)
+
+BUG=""
+PACKAGES=""
+TAG="${OJLUNI_MERGE_TARGET:-"$DEFAULT_TAG"}"
+
+function die()
+{
+  echo -e ${1}
+  if [[ -n "${2}" ]]
+  then
+    echo -e ""
+    echo -e "${USAGE}"
+  fi
+  exit 1
+}
+
+function validate_tag
+{
+  for expected in ${SUPPORTED_TAGS}
+  do
+    if [[ "${TAG}" == "${expected}" ]]
+    then
+      return
+    fi
+  done
+  die "Unknown tag: ${TAG}" "y"
+}
+
+function setup_env
+{
+  if [[ -z "${ANDROID_BUILD_TOP}" ]]
+  then
+    die "ANDROID_BUILD_TOP not found. You need to run lunch first."
+  fi
+
+  shopt -s expand_aliases
+  source "${ANDROID_BUILD_TOP}/libcore/tools/expected_upstream/install_tools.sh"
+}
+
+while [[ $# -gt 0 ]]; do
+  case ${1} in
+    -h|--help)
+      echo "${HELP}"
+      exit 0
+      ;;
+    -b|--bug)
+      BUG="${2}"
+      shift
+      ;;
+    -t|--tag)
+      TAG="${2}"
+      shift
+      ;;
+    *)
+      PACKAGES="${PACKAGES} ${1}"
+      ;;
+  esac
+  shift
+done
+
+if [[ -z "${PACKAGES}" ]]
+then
+  die "You need to specify at least one package to merge." "y"
+fi
+
+setup_env
+validate_tag
+
+if [[ -z "${BUG}" ]]
+then
+  pushd "${ANDROID_BUILD_TOP}/libcore"
+  BUG=$(git branch --show-current | grep -E -o "\<b\>[-/][0-9]+-" | grep -E -o "[0-9]+")
+  popd
+fi
+
+function ojluni-merge-class
+{
+  local method="${1}"
+  local name="${2}"
+  local version="${3}"
+
+  local first_arg="${name}"
+  local second_arg="${version}"
+
+  if [[ "${method}" == "add" ]]
+  then
+    first_arg="${version}"
+    second_arg="${name}"
+  fi
+  echo ojluni_modify_expectation "${method}" "${first_arg}" "${second_arg}"
+  ojluni_modify_expectation "${method}" "${first_arg}" "${second_arg}" || \
+    die "Failed to modify expectation file for ${name}"
+}
+
+function ojluni-merge-package
+{
+  local package="${1}"
+  local version="${2}"
+  local bug="${3}"
+  local package_path=$(echo "${package}" | sed --sandbox 's/\./\//'g)
+  local package_full_path="${ANDROID_BUILD_TOP}/libcore/ojluni/src/main/java/${package_path}"
+
+  pushd "${ANDROID_BUILD_TOP}/libcore"
+
+  for f in $(ls "${package_full_path}"/*.java)
+  do
+    local class_name=$(basename -s .java ${f})
+    local class_path="ojluni/src/main/java/${package_path}/${class_name}\.java"
+    local in_expected_upstream=$(grep "${class_path}" "${ANDROID_BUILD_TOP}/libcore/EXPECTED_UPSTREAM")
+    if [[ -n "${in_expected_upstream}" ]]
+    then
+      ojluni-merge-class modify "${package}.${class_name}" "${version}"
+    else
+      ojluni-merge-class add "${package}.${class_name}" "${version}"
+    fi
+  done
+
+  local version_id=$(echo "${version}" | grep -oE "^[^/]+")
+  local branch="aosp/upstream-open${version_id}"
+  local new_classes=$(git diff --name-only --diff-filter=D "${branch}" -- \
+    "src/java.base/share/classes/${package_path}" \
+    "ojluni/src/main/java/${package_path}")
+
+  for f in ${new_classes}
+  do
+    local class_name=$(basename -s .java ${f})
+    local class_path="ojluni/src/main/java/${package_path}/${class_name}\.java"
+    ojluni-merge-class add "${package}.${class_name}" "${version}"
+  done
+
+  if [[ -n "${bug}" ]]
+  then
+    echo ojluni_merge_to_master -b "${bug}"
+    ojluni_merge_to_master -b "${bug}" || die "Failed to merge ${package} to master"
+  else
+    echo ojluni_merge_to_master
+    ojluni_merge_to_master || die "Failed to merge ${package} to master"
+  fi
+
+  popd
+}
+
+for package in ${PACKAGES}
+do
+  echo "Merging '${package}' from ${TAG}"
+  ojluni-merge-package "${package}" "${TAG}" "${BUG}"
+done
diff --git a/tools/expected_upstream/ojluni_modify_expectation.py b/tools/expected_upstream/ojluni_modify_expectation.py
index 558b754..4c3934d 100755
--- a/tools/expected_upstream/ojluni_modify_expectation.py
+++ b/tools/expected_upstream/ojluni_modify_expectation.py
@@ -48,6 +48,7 @@
     'jdk11u/jdk-11+28',
     'jdk11u/jdk-11.0.13-ga',
     'jdk17u/jdk-17.0.2-ga',
+    'jdk17u/jdk-17.0.5-ga',
 ]