# bash completion for java, javac and javadoc              -*- shell-script -*-

# available path elements completion
_java_path()
{
    cur=${cur##*:}
    _filedir '@(jar|zip)'
}

# exact classpath determination
_java_find_classpath()
{
    local i

    # search first in current options
    for ((i = 1; i < cword; i++)); do
        if [[ ${words[i]} == -@(cp|classpath) ]]; then
            classpath=${words[i + 1]}
            break
        fi
    done

    # default to environment
    [[ ! -v classpath ]] && classpath=${CLASSPATH-}

    # default to current directory
    [[ -z $classpath ]] && classpath=.
}

# exact sourcepath determination
_java_find_sourcepath()
{
    local i

    # search first in current options
    for ((i = 1; i < cword; i++)); do
        if [[ ${words[i]} == -sourcepath ]]; then
            sourcepath=${words[i + 1]}
            break
        fi
    done

    # default to classpath
    if [[ ! -v sourcepath ]]; then
        local classpath
        _java_find_classpath
        sourcepath=$classpath
    fi
}

# available classes completion
_java_classes()
{
    local classpath i

    # find which classpath to use
    _java_find_classpath

    # convert package syntax to path syntax
    cur=${cur//.//}
    # parse each classpath element for classes
    for i in ${classpath//:/ }; do
        if [[ $i == *.@(jar|zip) && -r $i ]]; then
            if type zipinfo &>/dev/null; then
                COMPREPLY+=($(zipinfo -1 "$i" "$cur*" 2>/dev/null |
                    command grep '^[^$]*\.class$'))
            elif type unzip &>/dev/null; then
                # Last column, between entries consisting entirely of dashes
                COMPREPLY+=($(unzip -lq "$i" "$cur*" 2>/dev/null |
                    awk '$NF ~ /^-+$/ { flag=!flag; next };
                         flag && $NF ~ /^[^$]*\.class/ { print $NF }'))
            elif type jar &>/dev/null; then
                COMPREPLY+=($(jar tf "$i" "$cur" |
                    command grep '^[^$]*\.class$'))
            fi

        elif [[ -d $i ]]; then
            COMPREPLY+=(
                $(compgen -d -- "$i/$cur" | command sed -e "s|^$i/\(.*\)|\1.|")
                $(compgen -f -X '!*.class' -- "$i/$cur" |
                    command sed -e '/\$/d' -e "s|^$i/||")
            )
            [[ ${COMPREPLY-} == *.class ]] || compopt -o nospace

            # FIXME: if we have foo.class and foo/, the completion
            # returns "foo/"... how to give precedence to files
            # over directories?
        fi
    done

    if ((${#COMPREPLY[@]} != 0)); then
        # remove class extension
        COMPREPLY=(${COMPREPLY[@]%.class})
        # convert path syntax to package syntax
        COMPREPLY=(${COMPREPLY[@]//\//.})
    fi
}

# available packages completion
_java_packages()
{
    local sourcepath i

    # find which sourcepath to use
    _java_find_sourcepath

    # convert package syntax to path syntax
    cur=${cur//.//}
    # parse each sourcepath element for packages
    for i in ${sourcepath//:/ }; do
        if [[ -d $i ]]; then
            COMPREPLY+=($(command ls -F -d $i/$cur* 2>/dev/null |
                command sed -e 's|^'$i'/||'))
        fi
    done
    if ((${#COMPREPLY[@]} != 0)); then
        # keep only packages
        COMPREPLY=($(tr " " "\n" <<<"${COMPREPLY[@]}" | command grep "/$"))
        # remove packages extension
        COMPREPLY=(${COMPREPLY[@]%/})
        # convert path syntax to package syntax
        cur="${COMPREPLY[*]//\//.}"
    fi
}

# java completion
#
_java()
{
    local cur prev words cword
    _init_completion -n : || return

    local i

    for ((i = 1; i < cword; i++)); do
        case ${words[i]} in
            -cp | -classpath)
                ((i++)) # skip the classpath string.
                ;;
            -*)
                # this is an option, not a class/jarfile name.
                ;;
            *)
                # once we've seen a class, just do filename completion
                _filedir
                return
                ;;
        esac
    done

    case $cur in
        # standard option completions
        -verbose:*)
            COMPREPLY=($(compgen -W 'class gc jni' -- "${cur#*:}"))
            return
            ;;
        -javaagent:*)
            cur=${cur#*:}
            _filedir '@(jar|zip)'
            return
            ;;
        -agentpath:*)
            cur=${cur#*:}
            _filedir so
            return
            ;;
        # various non-standard option completions
        -splash:*)
            cur=${cur#*:}
            _filedir '@(gif|jp?(e)g|png)'
            return
            ;;
        -Xbootclasspath*:*)
            _java_path
            return
            ;;
        -Xcheck:*)
            COMPREPLY=($(compgen -W 'jni' -- "${cur#*:}"))
            return
            ;;
        -Xgc:*)
            COMPREPLY=($(compgen -W 'singlecon gencon singlepar genpar' \
                -- "${cur#*:}"))
            return
            ;;
        -Xgcprio:*)
            COMPREPLY=($(compgen -W 'throughput pausetime deterministic' \
                -- "${cur#*:}"))
            return
            ;;
        -Xloggc:* | -Xverboselog:*)
            cur=${cur#*:}
            _filedir
            return
            ;;
        -Xshare:*)
            COMPREPLY=($(compgen -W 'auto off on' -- "${cur#*:}"))
            return
            ;;
        -Xverbose:*)
            COMPREPLY=($(compgen -W 'memory load jni cpuinfo codegen opt
                gcpause gcreport' -- "${cur#*:}"))
            return
            ;;
        -Xverify:*)
            COMPREPLY=($(compgen -W 'all none remote' -- "${cur#*:}"))
            return
            ;;
        # the rest that we have no completions for
        -D* | -*:*)
            return
            ;;
    esac

    case $prev in
        -cp | -classpath)
            _java_path
            return
            ;;
    esac

    if [[ $cur == -* ]]; then
        COMPREPLY=($(compgen -W '$(_parse_help "$1" -help)' -- "$cur"))
        [[ $cur == -X* ]] &&
            COMPREPLY+=($(compgen -W '$(_parse_help "$1" -X)' -- "$cur"))
    else
        if [[ $prev == -jar ]]; then
            # jar file completion
            _filedir '[jw]ar'
        else
            # classes completion
            _java_classes
        fi
    fi

    [[ ${COMPREPLY-} == -*[:=] ]] && compopt -o nospace

    __ltrim_colon_completions "$cur"
} &&
    complete -F _java java

_javadoc()
{
    local cur prev words cword
    _init_completion || return

    case $prev in
        -overview | -helpfile)
            _filedir '?(x)htm?(l)'
            return
            ;;
        -doclet | -exclude | -subpackages | -source | -locale | -encoding | -windowtitle | \
            -doctitle | -header | -footer | -top | -bottom | -group | -noqualifier | -tag | \
            -charset | -sourcetab | -docencoding)
            return
            ;;
        -stylesheetfile)
            _filedir css
            return
            ;;
        -d | -link | -linkoffline)
            _filedir -d
            return
            ;;
        -classpath | -cp | -bootclasspath | -docletpath | -sourcepath | -extdirs | \
            -excludedocfilessubdir)
            _java_path
            return
            ;;
    esac

    # -linkoffline takes two arguments
    if [[ $cword -gt 2 && ${words[cword - 2]} == -linkoffline ]]; then
        _filedir -d
        return
    fi

    if [[ $cur == -* ]]; then
        COMPREPLY=($(compgen -W '$(_parse_help "$1" -help)' -- "$cur"))
    else
        # source files completion
        _filedir java
        # packages completion
        _java_packages
    fi
} &&
    complete -F _javadoc javadoc

_javac()
{
    local cur prev words cword
    _init_completion -n : || return

    case $prev in
        -d)
            _filedir -d
            return
            ;;
        -cp | -classpath | -bootclasspath | -sourcepath | -extdirs)
            _java_path
            return
            ;;
    esac

    if [[ $cur == -+([a-zA-Z0-9-_]):* ]]; then
        # Parse required options from -foo:{bar,quux,baz}
        local helpopt=-help
        [[ $cur == -X* ]] && helpopt=-X
        # For some reason there may be -g:none AND -g:{lines,source,vars};
        # convert the none case to the curly brace format so it parses like
        # the others.
        local opts=$("$1" $helpopt 2>&1 | command sed -e 's/-g:none/-g:{none}/' -ne \
            "s/^[[:space:]]*${cur%%:*}:{\([^}]\{1,\}\)}.*/\1/p")
        COMPREPLY=($(compgen -W "${opts//,/ }" -- "${cur#*:}"))
        return
    fi

    if [[ $cur == -* ]]; then
        COMPREPLY=($(compgen -W '$(_parse_help "$1" -help)' -- "$cur"))
        [[ $cur == -X* ]] &&
            COMPREPLY+=($(compgen -W '$(_parse_help "$1" -X)' -- "$cur"))
    else
        # source files completion
        _filedir java
    fi

    [[ ${COMPREPLY-} == -*[:=] ]] && compopt -o nospace

    __ltrim_colon_completions "$cur"
} &&
    complete -F _javac javac

# ex: filetype=sh
