#!/bin/bash
###
# Depends: gcj-jdk | default-jdk, perl, bash (>= 4.2)
# Recommends: fastjar, dlocate
###########
#  listdeps-jar -- list class path dependencies from given jars
#
#  Copyright © 2012  Dmitry Smirnov <onlyjob@member.fsf.org>
#  License: GPL-3+
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
#######
#    Debian may use this code under GPL-2+ license.
#######

if [ -z "$1" ]; then
    echo "Please invoke with one or more jar as argument."
    exit 1
fi

[[ "${BASH_VERSION/\(*/}" < "4.2" ]] \
&& echo "Warning: bash v4.1 leak memory on associative array look-ups. Please upgrade your bash or you may run into out-of-memory errors."

JCMD=$(which fastjar) \
|| JCMD=$(which jar)
PCMD="$(which jcf-dump) --print-constants java.lang.Object " \
|| PCMD="$(which javap) -l -verbose "

declare -A CLASSES
declare -A FILES
declare -A DEPJARS

DBGFNUM=0
function list_classes {		# jar
    JARLINK="$1"
    JARFILE=$(readlink -f "$1")
    TMPD=$(mktemp -d)
    pushd . >>/dev/null
    cd "${TMPD}" \
    && ${JCMD} xf "$JARFILE"
    for class in $(find -name '*.class'); do
	class="${class##./}"
	if [ -n "${FILES[class%%.class}]}" ]; then
	    echo "Already seen in ${FILES[class%%.class}]}"
	fi
	FILES[${class%%.class}]='.'
	OUT=$(${PCMD} "${class%%.class}") || echo "Error in command: ${PCMD} ${class%%.class}"
	REFS+=$(echo "$OUT" |perl -ne 'local $/; $_=<>; @M{m{(?:Class\s+name:\s+\d+="|\/\/class\s+)([^[#\$(:;)"\n]+)}gs}=(); print join "\n", sort %M;')
	for classref in $REFS; do
	    # skip if provided by given jars (saves RAM).
	    [ -z "${FILES[$classref]}" ] && CLASSES["${classref}"]="${JARLINK}"
	done
	(( DBGFNUM+=1 )) && (( DBGFNUM%10 )) || echo -n "." 1>&2		# print . to STDERR for every 10th file
    done
    popd >>/dev/null
    rm -rf "${TMPD}"
}

##### MAIN

echo "Scanning for classes:"
for arg in "$@"; do
    if [ -e "$arg" ]; then
	echo -n "  $arg "
	list_classes "$arg"
	echo
    fi
done

if [ -z "${!CLASSES[*]}" ]; then
    echo "Nothing to do..."
    exit 1
fi

declare -A SJARS
declare -A SCLASSES
echo -n "Scanning shared files: "
for sfile in /usr/share/java/*.jar; do
    sfile=$(readlink -f "$sfile")
	if [ -z "${SJARS[$sfile]}" ]; then
	    echo -n "." 1>&2	#to STDERR
	    for file in $(${JCMD} tf "$sfile"); do
		SCLASSES["${file%%.class}"]="$sfile"
	    done
	fi
    SJARS["$sfile"]="."
done
echo

echo "Calculating dependencies: "
for class in ${!CLASSES[@]}; do
    # check if found in given jars and print if not found
    if [ -z "${FILES[$class]}" ]; then
	# check if found in shared library and add to dependency list if found
	if [ -n "${SCLASSES[$class]}" ]; then
	    DEPJARS["${SCLASSES[$class]}"]="."
	    continue
	fi
	echo "  ${CLASSES[$class]}: $class"
    fi
done

if [ -n "${!DEPJARS[*]}" ]; then
    echo "Shared files needed:"
    for file in ${!DEPJARS[@]}; do
	echo -n "  "
	[ $(which dlocate) ] && PKG=$(dlocate "$file")
	[ -n "$PKG" ] && echo "$PKG" || echo "$file"
	classpath="$classpath $file"
    done
fi

exit
