#!/bin/bash

##############
# Print help #
##############
help() {
  echo ''
  echo 'Usage: check.sh [-jlnmv] <old_path> <new_path>'
  echo ''
  echo '  <old_path> :: Path to directory containing old jars'
  echo '  <new_path> :: Path to directory containing new jars'
  echo '  -j :: Check whether class files were built for proper Java platform'
  echo '  -l :: Check whether archives in <new_path> contain LICENSE file'
  echo '  -n :: Check whether archives in <new_path> contain NOTICE file'
  echo '  -m :: Check whether archives in <new_path> contain proper multirelease setup'
  echo '  -v :: Verbose output'
}

###############################
# Print verbose only messages #
###############################
# $@ - all arguments are printed
verbose() {
  if [ "${VERBOSE}" = 'true' ]; then
    echo "$@"
  fi
}

######################
# Print Java version #
######################
# $1 - JDK version number (decimal)
print_jdk() {
  case "${1}" in
    46)
      echo -n 'Java 1.2'
    ;;
    47)
      echo -n 'Java 1.3'
    ;;
    48)
      echo -n 'Java 1.4'
    ;;
    49)
      echo -n 'Java 1.5'
    ;;
    50)
      echo -n 'Java 1.6'
    ;;
    51)
      echo -n 'Java 1.7'
    ;;
    52)
      echo -n 'Java 1.8'
    ;;
    53)
      echo -n 'Java 9'
    ;;
    54)
      echo -n 'Java 10'
    ;;
    55)
      echo -n 'Java 11'
    ;;
    *)
      echo -n 'Unknown Java'
    ;;
  esac
}

#####################################
# Command line arguments processing #
#####################################
if [ "$#" -lt '2' ]; then
  echo '[ERROR] Missing <new_path> argument'
  if [ "$#" -lt '1' ]; then
    echo '[ERROR] Missing <old_path> argument'
  fi
  help
  exit 1
fi

# Switches config
CHECK_JAVA='false'
CHECK_LICENSE='false'
CHECK_MULTI='false'
CHECK_NOTICE='false'
VERBOSE='false'
ERROR='false'
# Which switch expects identifier
EXPECT_IDENT=''

# Command line arguments parser
while [ $# -gt 2 ]; do
  ARG=${1}
  CH1=${1:0:1}
  # Processing switch
  if [ "${CH1}" = '-' ]; then
    POS='1'
    while [ "${POS}" -lt ${#ARG} ]; do
      CH=${ARG:${POS}:1}
      case "${CH}" in
        j)
          CHECK_JAVA='true'
        ;;
        l)
          CHECK_LICENSE='true'
        ;;
        m)
          CHECK_MULTI='true'
        ;;
        n)
          CHECK_NOTICE='true'
        ;;
        v)
          VERBOSE='true'
        ;;
        *)
          echo '[ERROR] Unknown argument '"'-${CH}'"
          ERROR='true'
        ;;
      esac
      POS=$(( POS + 1 ))
    done
  # Processing identifier
  else
    # No identifier expected
    if [ -z "${EXPECT_IDENT}" ]; then
      echo '[ERROR] Switches argument shall start with '"'-"
      ERROR='true'
    # Some identifier expected
    else
      case "${EXPECT_IDENT}" in
        *)
          echo '[ERROR] Internal error: Switch -'${EXPECT_IDENT}' does not support additional value'
          ERROR='true'
        ;;
      esac
    fi
  fi
  shift
done
unset ARG CH1
if [ "${ERROR}" = 'true' ]; then
  help
  exit
fi

OLD_PATH=${1}
NEW_PATH=${2}
CHECK_OK='true'

###############
# Print setup #
###############
if [ "${VERBOSE}" = 'true' ]; then
  echo '[INFO] Setup:'
  echo "[INFO]   - Old JAR path: ${OLD_PATH}"
  echo "[INFO]   - New JAR path: ${NEW_PATH}"
  if [ "${CHECK_JAVA}" = 'true' ]; then
    echo '[INFO]   - Checking target build Java version in class files'
    echo '[INFO]     - Java 1.8 in regular classes'
    echo '[INFO]     - Java 9 in module-info.class and META-INF/versions'
  fi
  if [ "${CHECK_LICENSE}" = 'true' ]; then
    echo '[INFO]   - Checking META-INF/LICENSE[.txt|.md] existence'
  fi
  if [ "${CHECK_NOTICE}" = 'true' ]; then
    echo '[INFO]   - Checking META-INF/NOTICE[.txt|.md] existence'
  fi
  if [ "${CHECK_MULTI}" = 'true' ]; then
    echo '[INFO]   - Checking proper multi-release setup'
  fi
fi

#####################
# Process JAR files #
#####################
NEW_FILES=()
OLD_FILES=()
OLD_FILES_LS=()
# Populate old files list
INDEX='0'
verbose "[INFO] Scanning ${OLD_PATH} directory"
for old_file in $(find ${OLD_PATH} -name '*.jar' -type f -depth 1 -print); do
  verbose "[INFO] - ${old_file}"
  OLD_FILES_LS[${INDEX}]="${old_file}"
  (( INDEX++ ))
done
# Iterate over new files and compare them with old files list
INDEX='0'
echo "[INFO] Scanning ${NEW_PATH} directory"
for new_file in $(find ${NEW_PATH} -name '*.jar' -type f -depth 1 -print); do
  echo "[INFO] - JAR: ${new_file}"
  NEW_FILES[${INDEX}]="${new_file}"

  # jaxb-api and jax-ws-api are exceptions
  if [[ "${new_file}" =~ jakarta\.xml\.bind-api.* ]]; then
    OLD_FILE_PATTERN="${OLD_PATH}/"'jaxb-api-.*\.jar'
    verbose "[INFO]   - JAXB API pattern:   ${OLD_FILE_PATTERN}"

  elif [[ "${new_file}" =~ jakarta\.xml\.ws-api.* ]]; then
    OLD_FILE_PATTERN="${OLD_PATH}/"'jaxws-api-.*\.jar'
    verbose "[INFO]   - JAX WS API pattern: ${OLD_FILE_PATTERN}"

  # jakarta. files were javax before
  elif [[ "${new_file}" =~ jakarta\..* ]]; then
    OLD_FILE_NAME="${new_file/jakarta\./javax.}"
    OLD_FILE_NAME="${OLD_FILE_NAME/${NEW_PATH}/${OLD_PATH}}"
    OLD_FILE_PATTERN=$(echo ${OLD_FILE_NAME} | sed -e 's/[0-9][0-9]*\(\.[0-9][0-9]*\)*/.*/' -e 's/\.jar/\\\.jar/')
    verbose "[INFO]   - Javax pattern:      ${OLD_FILE_PATTERN}"

  else
    OLD_FILE_NAME="${new_file/${NEW_PATH}/${OLD_PATH}}"
    OLD_FILE_PATTERN=$(echo ${OLD_FILE_NAME} | sed -e 's/[0-9][0-9]*\(\.[0-9][0-9]*\)*/.*/' -e 's/\.jar/\\\.jar/')
    verbose "[INFO]   - Pattern:            ${OLD_FILE_PATTERN}"
  fi

  # Find corresponding jar file in old directory
  OLD_SIZE=${#OLD_FILES_LS[@]}
  OLD_INDEX=0;
  OLD_FILE=''
  while [ "${OLD_INDEX}" -lt "${OLD_SIZE}" -a -z "${OLD_FILE}" ]; do
    if [[ "${OLD_FILES_LS[${OLD_INDEX}]}" =~ ${OLD_FILE_PATTERN} ]]; then
      OLD_FILE="${OLD_FILES_LS[${OLD_INDEX}]}"
      OLD_FILES[${INDEX}]="${OLD_FILE}"
      OLD_FILES_LS[${OLD_INDEX}]=''
      verbose "[INFO]   - Old file matched:   ${OLD_FILE}"
    fi
    (( OLD_INDEX++ ))
  done
  if [ -z "${OLD_FILE}" ]; then
    echo "[ERROR]  - Old file not found"
    CHECK_OK='false'
  fi
  (( INDEX++ ))
done

# Check unmatched files from old directory
OLD_INDEX=0;
while [ "${OLD_INDEX}" -lt "${OLD_SIZE}" ]; do
  if [ -n "${OLD_FILES_LS[${OLD_INDEX}]}" ]; then
    echo "[ERROR] Unmatched old file: ${OLD_FILES_LS[${OLD_INDEX}]}"
    CHECK_OK='false'
  fi
  (( OLD_INDEX++ ))
done

########################
# Process JARs content #
########################
INDEX='0'
for jar_file in `find ${NEW_PATH} -name '*.jar' -type f -print`; do
  echo "[INFO] Checking JAR: ${jar_file}"

  # Simple checks can work just with JAR listing
  if [ "${CHECK_LICENSE}" = 'true' -o "${CHECK_NOTICE}" = 'true' ]; then
    IS_LICENSE='false'
    IS_NOTICE='false'
    for file in $(jar tvf ${jar_file} | grep 'META-INF' | awk '{print $8}'); do
      if [ "${CHECK_LICENSE}" = 'true' ] && [[ "${file}" =~ 'META-INF/LICENSE' ]]; then
        IS_LICENSE='true'
        verbose "[INFO] - License file: ${file}"
      fi
      if [ "${CHECK_NOTICE}" = 'true' ] && [[ "${file}" =~ 'META-INF/NOTICE' ]]; then
        IS_NOTICE='true'
        verbose "[INFO] - Notice file: ${file}"
      fi
    done
    if [ "${CHECK_LICENSE}" = 'true' -a "${IS_LICENSE}" != 'true' ]; then
      echo "[ERROR] Missing META-INF/LICENSE in ${jar_file}"
    fi
    if [ "${CHECK_NOTICE}" = 'true' -a "${IS_NOTICE}" != 'true' ]; then
      echo "[ERROR] Missing META-INF/NOTICE in ${jar_file}"
    fi
  fi

  # Some check require JAR to be extracted
  if [ ${CHECK_JAVA} = 'true' -o ${CHECK_MULTI} = 'true' ]; then
    CUR_PATH=`pwd`
    TMP_DIR="jar_"`echo "${jar_file}" | sed -e 's/.*\///g' -e 's/-*[0-9][0-9]*\(\.[0-9][0-9]*\)*.*//g'`
    
    mkdir ${TMP_DIR}
    verbose "[INFO] - Created directory ${TMP_DIR} for content of ${jar_file}"
    CH1_JAR=${jar_file:0:1}
    if [ "${CH1_JAR}" = '/' ]; then
      PATH_PREFIX=''
    else
      PATH_PREFIX='../'
    fi

    if cd ${TMP_DIR} && jar xvf "${PATH_PREFIX}${jar_file}" > /dev/null; then
       verbose "[INFO] - Extracted ${jar_file}"

      # Java version check
      if [ ${CHECK_JAVA} = 'true' ]; then
        for class_file in $(find . -name '*.class' -type f -print); do
          CLASS_ID=`hexdump -s 7 -n 1 -e '16/1 "%02i" "\n"' ${class_file} | awk '{print $1}'`
          verbose "[INFO] - Class ${class_file}: "`print_jdk "${CLASS_ID}"`
          # module-info.class
          if [[ "${class_file}" =~ '/module-info.class' ]]; then
            if [ "${CLASS_ID}" -ne '53' ]; then
              echo "[ERROR] ${class_file} must be build for Java version 9"
              CHECK_OK='false'
            fi
          # META-INF/versions
          elif [[ "${class_file}" =~ 'META-INF/versions' ]]; then
            if [ "${CLASS_ID}" -ne '53' ]; then
              echo "[ERROR] ${class_file} in META-INF/versions must be build for Java version 9"
              CHECK_OK='false'
            fi
          # Regular class
          else
            if [ "${CLASS_ID}" -gt '52' ]; then
              echo "[ERROR] ${class_file} must be build for Java version <= 1.8"
              CHECK_OK='false'
            fi
          fi
        done
      fi

      # Multi-release OSGI header check
      if [ ${CHECK_MULTI} = 'true' ]; then
        # Check whether OSGI header is required
        if [ -d 'META-INF/versions' ] && find 'META-INF/versions' -name '*.class' -type f > /dev/null; then
            if [ -f 'META-INF/MANIFEST.MF' ]; then
              MULTI_HD=`grep 'Multi-Release:' META-INF/MANIFEST.MF | tr -d '\r'`
              verbose "[INFO] - OSGI Header ${MULTI_HD}"
              MULTI_HD_VALUE="$(echo ${MULTI_HD} | sed -e 's/Multi-Release: *//')"
              if [ "${MULTI_HD_VALUE}" != 'true' ]; then
                echo "[ERROR] ${class_file}: Missing Multi-Release: true OSGI header for multirelease jar"
              fi
            else
              echo "[ERROR] ${class_file}: Missing META-INF/MANIFEST.MF in multi-release jar"
              CHECK_OK='false'
            fi
        fi
      fi

      cd ${CUR_PATH}
    else
      echo "[ERROR] - Extraction if ${jar_file} failed"
      CHECK_OK='false'
    fi
    
    rm -rf ${TMP_DIR}
    if [ "${VERBOSE}" = 'true' ]; then
      echo "[INFO] - Deleted directory ${TMP_DIR}"
    fi
  fi
  (( INDEX++ ))
done

###################
# Evaluate result #
###################
if [ ${CHECK_OK} = 'false' ]; then
  echo '[ERROR] JARs check failed'
  exit 1
fi