diff --git a/.hgtags b/.hgtags index 462b081a399..9ac115a08b5 100644 --- a/.hgtags +++ b/.hgtags @@ -345,3 +345,4 @@ f9bcdce2df26678c3fe468130b535c0342c69b89 jdk-9+99 4379223f8806626852c46c52d4e7a27a584b406e jdk-9+100 80f67512daa15cf37b4825c1c62a675d524d7c49 jdk-9+101 2dc4c11fe48831854916d53c3913bdb7d49023ea jdk-9+102 +4a652e4ca9523422149958673033e0ac740d5e1e jdk-9+103 diff --git a/.hgtags-top-repo b/.hgtags-top-repo index ae78a92b7f7..9de50aa4824 100644 --- a/.hgtags-top-repo +++ b/.hgtags-top-repo @@ -345,3 +345,4 @@ cf1dc4c035fb84693d4ae5ad818785cb4d1465d1 jdk9-b90 c1f30ac14db0eaff398429c04cd9fab92e1b4b2a jdk-9+100 c4d72a1620835b5d657b7b6792c2879367d0154f jdk-9+101 6406ecf5d39482623225bb1b3098c2cac6f7d450 jdk-9+102 +47d6462e514b2097663305a57d9c844c15d5b609 jdk-9+103 diff --git a/common/autoconf/generated-configure.sh b/common/autoconf/generated-configure.sh index c295b3bf838..0fdc4605014 100644 --- a/common/autoconf/generated-configure.sh +++ b/common/autoconf/generated-configure.sh @@ -787,8 +787,6 @@ CPP ac_ct_CXX CXXFLAGS CXX -ac_ct_PROPER_COMPILER_CXX -PROPER_COMPILER_CXX TOOLCHAIN_PATH_CXX POTENTIAL_CXX OBJEXT @@ -798,8 +796,6 @@ CPPFLAGS LDFLAGS CFLAGS CC -ac_ct_PROPER_COMPILER_CC -PROPER_COMPILER_CC TOOLCHAIN_PATH_CC POTENTIAL_CC TOOLCHAIN_VERSION @@ -4119,16 +4115,6 @@ pkgadd_help() { - # -g is already added by ENABLE_DEBUG_SYMBOLS and the hotspot makefiles - # will basically do slowdebug builds when DEBUG_BINARIES is set for - # fastdebug builds - DEBUG_BINARIES=false - # Fastdebug builds with this setting will essentially be slowdebug - # in hotspot. - # -g is already added by ENABLE_DEBUG_SYMBOLS and the hotspot makefiles - # will basically do slowdebug builds when DEBUG_BINARIES is set for - # fastdebug builds - DEBUG_BINARIES=false # # Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. @@ -4849,7 +4835,7 @@ VS_SDK_PLATFORM_NAME_2013= #CUSTOM_AUTOCONF_INCLUDE # Do not change or remove the following line, it is needed for consistency checks: -DATE_WHEN_GENERATED=1453385294 +DATE_WHEN_GENERATED=1454146111 ############################################################################### # @@ -32022,12 +32008,10 @@ $as_echo "$as_me: Rewriting CC to \"$new_complete\"" >&6;} fi TEST_COMPILER="$CC" - # Don't remove symbolic links on AIX because 'xlc_r' and 'xlC_r' may all be links - # to 'xlc' but it is crucial that we invoke the compiler with the right name! - if test "x$OPENJDK_BUILD_OS" != xaix; then - # FIXME: This test should not be needed anymore; we don't do that for any platform. - { $as_echo "$as_me:${as_lineno-$LINENO}: checking resolved symbolic links for CC" >&5 + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking resolved symbolic links for CC" >&5 $as_echo_n "checking resolved symbolic links for CC... " >&6; } + SYMLINK_ORIGINAL="$TEST_COMPILER" if test "x$OPENJDK_BUILD_OS" != xwindows; then # Follow a chain of symbolic links. Use readlink @@ -32046,13 +32030,13 @@ $as_echo_n "checking resolved symbolic links for CC... " >&6; } fi if test "x$READLINK" != x; then - TEST_COMPILER=`$READLINK -f $TEST_COMPILER` + SYMLINK_ORIGINAL=`$READLINK -f $SYMLINK_ORIGINAL` else # Save the current directory for restoring afterwards STARTDIR=$PWD COUNTER=0 - sym_link_dir=`$DIRNAME $TEST_COMPILER` - sym_link_file=`$BASENAME $TEST_COMPILER` + sym_link_dir=`$DIRNAME $SYMLINK_ORIGINAL` + sym_link_file=`$BASENAME $SYMLINK_ORIGINAL` cd $sym_link_dir # Use -P flag to resolve symlinks in directories. cd `$THEPWDCMD -P` @@ -32072,474 +32056,25 @@ $as_echo_n "checking resolved symbolic links for CC... " >&6; } let COUNTER=COUNTER+1 done cd $STARTDIR - TEST_COMPILER=$sym_link_dir/$sym_link_file + SYMLINK_ORIGINAL=$sym_link_dir/$sym_link_file fi fi - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $TEST_COMPILER" >&5 -$as_echo "$TEST_COMPILER" >&6; } - fi - { $as_echo "$as_me:${as_lineno-$LINENO}: checking if CC is disguised ccache" >&5 -$as_echo_n "checking if CC is disguised ccache... " >&6; } - - COMPILER_BASENAME=`$BASENAME "$TEST_COMPILER"` - if test "x$COMPILER_BASENAME" = "xccache"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes, trying to find proper $COMPILER_NAME compiler" >&5 -$as_echo "yes, trying to find proper $COMPILER_NAME compiler" >&6; } - # We /usr/lib/ccache in the path, so cc is a symlink to /usr/bin/ccache. - # We want to control ccache invocation ourselves, so ignore this cc and try - # searching again. - - # Remove the path to the fake ccache cc from the PATH - RETRY_COMPILER_SAVED_PATH="$PATH" - COMPILER_DIRNAME=`$DIRNAME $CC` - PATH="`$ECHO $PATH | $SED -e "s,$COMPILER_DIRNAME,,g" -e "s,::,:,g" -e "s,^:,,g"`" - - # Try again looking for our compiler - if test -n "$ac_tool_prefix"; then - for ac_prog in $TOOLCHAIN_CC_BINARY - do - # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. -set dummy $ac_tool_prefix$ac_prog; ac_word=$2 -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -$as_echo_n "checking for $ac_word... " >&6; } -if ${ac_cv_prog_PROPER_COMPILER_CC+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test -n "$PROPER_COMPILER_CC"; then - ac_cv_prog_PROPER_COMPILER_CC="$PROPER_COMPILER_CC" # Let the user override the test. -else -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then - ac_cv_prog_PROPER_COMPILER_CC="$ac_tool_prefix$ac_prog" - $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - -fi -fi -PROPER_COMPILER_CC=$ac_cv_prog_PROPER_COMPILER_CC -if test -n "$PROPER_COMPILER_CC"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PROPER_COMPILER_CC" >&5 -$as_echo "$PROPER_COMPILER_CC" >&6; } -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } -fi - - - test -n "$PROPER_COMPILER_CC" && break - done -fi -if test -z "$PROPER_COMPILER_CC"; then - ac_ct_PROPER_COMPILER_CC=$PROPER_COMPILER_CC - for ac_prog in $TOOLCHAIN_CC_BINARY -do - # Extract the first word of "$ac_prog", so it can be a program name with args. -set dummy $ac_prog; ac_word=$2 -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -$as_echo_n "checking for $ac_word... " >&6; } -if ${ac_cv_prog_ac_ct_PROPER_COMPILER_CC+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test -n "$ac_ct_PROPER_COMPILER_CC"; then - ac_cv_prog_ac_ct_PROPER_COMPILER_CC="$ac_ct_PROPER_COMPILER_CC" # Let the user override the test. -else -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then - ac_cv_prog_ac_ct_PROPER_COMPILER_CC="$ac_prog" - $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - -fi -fi -ac_ct_PROPER_COMPILER_CC=$ac_cv_prog_ac_ct_PROPER_COMPILER_CC -if test -n "$ac_ct_PROPER_COMPILER_CC"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_PROPER_COMPILER_CC" >&5 -$as_echo "$ac_ct_PROPER_COMPILER_CC" >&6; } -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } -fi - - - test -n "$ac_ct_PROPER_COMPILER_CC" && break -done - - if test "x$ac_ct_PROPER_COMPILER_CC" = x; then - PROPER_COMPILER_CC="" + if test "x$TEST_COMPILER" = "x$SYMLINK_ORIGINAL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no symlink" >&5 +$as_echo "no symlink" >&6; } else - case $cross_compiling:$ac_tool_warned in -yes:) -{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 -$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} -ac_tool_warned=yes ;; -esac - PROPER_COMPILER_CC=$ac_ct_PROPER_COMPILER_CC - fi -fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SYMLINK_ORIGINAL" >&5 +$as_echo "$SYMLINK_ORIGINAL" >&6; } - - # Only process if variable expands to non-empty - - if test "x$PROPER_COMPILER_CC" != x; then - if test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.cygwin"; then - - # First separate the path from the arguments. This will split at the first - # space. - complete="$PROPER_COMPILER_CC" - path="${complete%% *}" - tmp="$complete EOL" - arguments="${tmp#* }" - - # Input might be given as Windows format, start by converting to - # unix format. - new_path=`$CYGPATH -u "$path"` - - # Now try to locate executable using which - new_path=`$WHICH "$new_path" 2> /dev/null` - # bat and cmd files are not always considered executable in cygwin causing which - # to not find them - if test "x$new_path" = x \ - && test "x`$ECHO \"$path\" | $GREP -i -e \"\\.bat$\" -e \"\\.cmd$\"`" != x \ - && test "x`$LS \"$path\" 2>/dev/null`" != x; then - new_path=`$CYGPATH -u "$path"` - fi - if test "x$new_path" = x; then - # Oops. Which didn't find the executable. - # The splitting of arguments from the executable at a space might have been incorrect, - # since paths with space are more likely in Windows. Give it another try with the whole - # argument. - path="$complete" - arguments="EOL" - new_path=`$CYGPATH -u "$path"` - new_path=`$WHICH "$new_path" 2> /dev/null` - # bat and cmd files are not always considered executable in cygwin causing which - # to not find them - if test "x$new_path" = x \ - && test "x`$ECHO \"$path\" | $GREP -i -e \"\\.bat$\" -e \"\\.cmd$\"`" != x \ - && test "x`$LS \"$path\" 2>/dev/null`" != x; then - new_path=`$CYGPATH -u "$path"` + # We can't handle ccache by gcc wrappers, since we need to know if we're + # using ccache. Instead ccache usage must be controlled by a configure option. + COMPILER_BASENAME=`$BASENAME "$SYMLINK_ORIGINAL"` + if test "x$COMPILER_BASENAME" = "xccache"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: Please use --enable-ccache instead of providing a wrapped compiler." >&5 +$as_echo "$as_me: Please use --enable-ccache instead of providing a wrapped compiler." >&6;} + as_fn_error $? "$TEST_COMPILER is a symbolic link to ccache. This is not supported." "$LINENO" 5 fi - if test "x$new_path" = x; then - # It's still not found. Now this is an unrecoverable error. - { $as_echo "$as_me:${as_lineno-$LINENO}: The path of PROPER_COMPILER_CC, which resolves as \"$complete\", is not found." >&5 -$as_echo "$as_me: The path of PROPER_COMPILER_CC, which resolves as \"$complete\", is not found." >&6;} - has_space=`$ECHO "$complete" | $GREP " "` - if test "x$has_space" != x; then - { $as_echo "$as_me:${as_lineno-$LINENO}: You might be mixing spaces in the path and extra arguments, which is not allowed." >&5 -$as_echo "$as_me: You might be mixing spaces in the path and extra arguments, which is not allowed." >&6;} - fi - as_fn_error $? "Cannot locate the the path of PROPER_COMPILER_CC" "$LINENO" 5 - fi - fi - - # Cygwin tries to hide some aspects of the Windows file system, such that binaries are - # named .exe but called without that suffix. Therefore, "foo" and "foo.exe" are considered - # the same file, most of the time (as in "test -f"). But not when running cygpath -s, then - # "foo.exe" is OK but "foo" is an error. - # - # This test is therefore slightly more accurate than "test -f" to check for file presence. - # It is also a way to make sure we got the proper file name for the real test later on. - test_shortpath=`$CYGPATH -s -m "$new_path" 2> /dev/null` - if test "x$test_shortpath" = x; then - # Short path failed, file does not exist as specified. - # Try adding .exe or .cmd - if test -f "${new_path}.exe"; then - input_to_shortpath="${new_path}.exe" - elif test -f "${new_path}.cmd"; then - input_to_shortpath="${new_path}.cmd" - else - { $as_echo "$as_me:${as_lineno-$LINENO}: The path of PROPER_COMPILER_CC, which resolves as \"$new_path\", is invalid." >&5 -$as_echo "$as_me: The path of PROPER_COMPILER_CC, which resolves as \"$new_path\", is invalid." >&6;} - { $as_echo "$as_me:${as_lineno-$LINENO}: Neither \"$new_path\" nor \"$new_path.exe/cmd\" can be found" >&5 -$as_echo "$as_me: Neither \"$new_path\" nor \"$new_path.exe/cmd\" can be found" >&6;} - as_fn_error $? "Cannot locate the the path of PROPER_COMPILER_CC" "$LINENO" 5 - fi - else - input_to_shortpath="$new_path" - fi - - # Call helper function which possibly converts this using DOS-style short mode. - # If so, the updated path is stored in $new_path. - new_path="$input_to_shortpath" - - input_path="$input_to_shortpath" - # Check if we need to convert this using DOS-style short mode. If the path - # contains just simple characters, use it. Otherwise (spaces, weird characters), - # take no chances and rewrite it. - # Note: m4 eats our [], so we need to use [ and ] instead. - has_forbidden_chars=`$ECHO "$input_path" | $GREP [^-._/a-zA-Z0-9]` - if test "x$has_forbidden_chars" != x; then - # Now convert it to mixed DOS-style, short mode (no spaces, and / instead of \) - shortmode_path=`$CYGPATH -s -m -a "$input_path"` - path_after_shortmode=`$CYGPATH -u "$shortmode_path"` - if test "x$path_after_shortmode" != "x$input_to_shortpath"; then - # Going to short mode and back again did indeed matter. Since short mode is - # case insensitive, let's make it lowercase to improve readability. - shortmode_path=`$ECHO "$shortmode_path" | $TR 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` - # Now convert it back to Unix-style (cygpath) - input_path=`$CYGPATH -u "$shortmode_path"` - new_path="$input_path" - fi - fi - - test_cygdrive_prefix=`$ECHO $input_path | $GREP ^/cygdrive/` - if test "x$test_cygdrive_prefix" = x; then - # As a simple fix, exclude /usr/bin since it's not a real path. - if test "x`$ECHO $input_to_shortpath | $GREP ^/usr/bin/`" = x; then - # The path is in a Cygwin special directory (e.g. /home). We need this converted to - # a path prefixed by /cygdrive for fixpath to work. - new_path="$CYGWIN_ROOT_PATH$input_path" - fi - fi - - # remove trailing .exe if any - new_path="${new_path/%.exe/}" - - elif test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.msys"; then - - # First separate the path from the arguments. This will split at the first - # space. - complete="$PROPER_COMPILER_CC" - path="${complete%% *}" - tmp="$complete EOL" - arguments="${tmp#* }" - - # Input might be given as Windows format, start by converting to - # unix format. - new_path="$path" - - windows_path="$new_path" - if test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.cygwin"; then - unix_path=`$CYGPATH -u "$windows_path"` - new_path="$unix_path" - elif test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.msys"; then - unix_path=`$ECHO "$windows_path" | $SED -e 's,^\\(.\\):,/\\1,g' -e 's,\\\\,/,g'` - new_path="$unix_path" - fi - - - # Now try to locate executable using which - new_path=`$WHICH "$new_path" 2> /dev/null` - - if test "x$new_path" = x; then - # Oops. Which didn't find the executable. - # The splitting of arguments from the executable at a space might have been incorrect, - # since paths with space are more likely in Windows. Give it another try with the whole - # argument. - path="$complete" - arguments="EOL" - new_path="$path" - - windows_path="$new_path" - if test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.cygwin"; then - unix_path=`$CYGPATH -u "$windows_path"` - new_path="$unix_path" - elif test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.msys"; then - unix_path=`$ECHO "$windows_path" | $SED -e 's,^\\(.\\):,/\\1,g' -e 's,\\\\,/,g'` - new_path="$unix_path" - fi - - - new_path=`$WHICH "$new_path" 2> /dev/null` - # bat and cmd files are not always considered executable in MSYS causing which - # to not find them - if test "x$new_path" = x \ - && test "x`$ECHO \"$path\" | $GREP -i -e \"\\.bat$\" -e \"\\.cmd$\"`" != x \ - && test "x`$LS \"$path\" 2>/dev/null`" != x; then - new_path="$path" - - windows_path="$new_path" - if test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.cygwin"; then - unix_path=`$CYGPATH -u "$windows_path"` - new_path="$unix_path" - elif test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.msys"; then - unix_path=`$ECHO "$windows_path" | $SED -e 's,^\\(.\\):,/\\1,g' -e 's,\\\\,/,g'` - new_path="$unix_path" - fi - - fi - - if test "x$new_path" = x; then - # It's still not found. Now this is an unrecoverable error. - { $as_echo "$as_me:${as_lineno-$LINENO}: The path of PROPER_COMPILER_CC, which resolves as \"$complete\", is not found." >&5 -$as_echo "$as_me: The path of PROPER_COMPILER_CC, which resolves as \"$complete\", is not found." >&6;} - has_space=`$ECHO "$complete" | $GREP " "` - if test "x$has_space" != x; then - { $as_echo "$as_me:${as_lineno-$LINENO}: You might be mixing spaces in the path and extra arguments, which is not allowed." >&5 -$as_echo "$as_me: You might be mixing spaces in the path and extra arguments, which is not allowed." >&6;} - fi - as_fn_error $? "Cannot locate the the path of PROPER_COMPILER_CC" "$LINENO" 5 - fi - fi - - # Now new_path has a complete unix path to the binary - if test "x`$ECHO $new_path | $GREP ^/bin/`" != x; then - # Keep paths in /bin as-is, but remove trailing .exe if any - new_path="${new_path/%.exe/}" - # Do not save /bin paths to all_fixpath_prefixes! - else - # Not in mixed or Windows style, start by that. - new_path=`cmd //c echo $new_path` - - input_path="$new_path" - # Check if we need to convert this using DOS-style short mode. If the path - # contains just simple characters, use it. Otherwise (spaces, weird characters), - # take no chances and rewrite it. - # Note: m4 eats our [], so we need to use [ and ] instead. - has_forbidden_chars=`$ECHO "$input_path" | $GREP [^-_/:a-zA-Z0-9]` - if test "x$has_forbidden_chars" != x; then - # Now convert it to mixed DOS-style, short mode (no spaces, and / instead of \) - new_path=`cmd /c "for %A in (\"$input_path\") do @echo %~sA"|$TR \\\\\\\\ / | $TR 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` - fi - - # Output is in $new_path - - windows_path="$new_path" - if test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.cygwin"; then - unix_path=`$CYGPATH -u "$windows_path"` - new_path="$unix_path" - elif test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.msys"; then - unix_path=`$ECHO "$windows_path" | $SED -e 's,^\\(.\\):,/\\1,g' -e 's,\\\\,/,g'` - new_path="$unix_path" - fi - - # remove trailing .exe if any - new_path="${new_path/%.exe/}" - - # Save the first 10 bytes of this path to the storage, so fixpath can work. - all_fixpath_prefixes=("${all_fixpath_prefixes[@]}" "${new_path:0:10}") - fi - - else - # We're on a unix platform. Hooray! :) - # First separate the path from the arguments. This will split at the first - # space. - complete="$PROPER_COMPILER_CC" - path="${complete%% *}" - tmp="$complete EOL" - arguments="${tmp#* }" - - # Cannot rely on the command "which" here since it doesn't always work. - is_absolute_path=`$ECHO "$path" | $GREP ^/` - if test -z "$is_absolute_path"; then - # Path to executable is not absolute. Find it. - IFS_save="$IFS" - IFS=: - for p in $PATH; do - if test -f "$p/$path" && test -x "$p/$path"; then - new_path="$p/$path" - break - fi - done - IFS="$IFS_save" - else - # This is an absolute path, we can use it without further modifications. - new_path="$path" - fi - - if test "x$new_path" = x; then - { $as_echo "$as_me:${as_lineno-$LINENO}: The path of PROPER_COMPILER_CC, which resolves as \"$complete\", is not found." >&5 -$as_echo "$as_me: The path of PROPER_COMPILER_CC, which resolves as \"$complete\", is not found." >&6;} - has_space=`$ECHO "$complete" | $GREP " "` - if test "x$has_space" != x; then - { $as_echo "$as_me:${as_lineno-$LINENO}: This might be caused by spaces in the path, which is not allowed." >&5 -$as_echo "$as_me: This might be caused by spaces in the path, which is not allowed." >&6;} - fi - as_fn_error $? "Cannot locate the the path of PROPER_COMPILER_CC" "$LINENO" 5 - fi - fi - - # Now join together the path and the arguments once again - if test "x$arguments" != xEOL; then - new_complete="$new_path ${arguments% *}" - else - new_complete="$new_path" - fi - - if test "x$complete" != "x$new_complete"; then - PROPER_COMPILER_CC="$new_complete" - { $as_echo "$as_me:${as_lineno-$LINENO}: Rewriting PROPER_COMPILER_CC to \"$new_complete\"" >&5 -$as_echo "$as_me: Rewriting PROPER_COMPILER_CC to \"$new_complete\"" >&6;} - fi - fi - - PATH="$RETRY_COMPILER_SAVED_PATH" - - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for resolved symbolic links for CC" >&5 -$as_echo_n "checking for resolved symbolic links for CC... " >&6; } - - if test "x$OPENJDK_BUILD_OS" != xwindows; then - # Follow a chain of symbolic links. Use readlink - # where it exists, else fall back to horribly - # complicated shell code. - if test "x$READLINK_TESTED" != yes; then - # On MacOSX there is a readlink tool with a different - # purpose than the GNU readlink tool. Check the found readlink. - ISGNU=`$READLINK --version 2>&1 | $GREP GNU` - if test "x$ISGNU" = x; then - # A readlink that we do not know how to use. - # Are there other non-GNU readlinks out there? - READLINK_TESTED=yes - READLINK= - fi - fi - - if test "x$READLINK" != x; then - PROPER_COMPILER_CC=`$READLINK -f $PROPER_COMPILER_CC` - else - # Save the current directory for restoring afterwards - STARTDIR=$PWD - COUNTER=0 - sym_link_dir=`$DIRNAME $PROPER_COMPILER_CC` - sym_link_file=`$BASENAME $PROPER_COMPILER_CC` - cd $sym_link_dir - # Use -P flag to resolve symlinks in directories. - cd `$THEPWDCMD -P` - sym_link_dir=`$THEPWDCMD -P` - # Resolve file symlinks - while test $COUNTER -lt 20; do - ISLINK=`$LS -l $sym_link_dir/$sym_link_file | $GREP '\->' | $SED -e 's/.*-> \(.*\)/\1/'` - if test "x$ISLINK" == x; then - # This is not a symbolic link! We are done! - break - fi - # Again resolve directory symlinks since the target of the just found - # link could be in a different directory - cd `$DIRNAME $ISLINK` - sym_link_dir=`$THEPWDCMD -P` - sym_link_file=`$BASENAME $ISLINK` - let COUNTER=COUNTER+1 - done - cd $STARTDIR - PROPER_COMPILER_CC=$sym_link_dir/$sym_link_file - fi - fi - - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PROPER_COMPILER_CC" >&5 -$as_echo "$PROPER_COMPILER_CC" >&6; } - CC="$PROPER_COMPILER_CC" - else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no, keeping CC" >&5 -$as_echo "no, keeping CC" >&6; } fi @@ -33770,12 +33305,10 @@ $as_echo "$as_me: Rewriting CXX to \"$new_complete\"" >&6;} fi TEST_COMPILER="$CXX" - # Don't remove symbolic links on AIX because 'xlc_r' and 'xlC_r' may all be links - # to 'xlc' but it is crucial that we invoke the compiler with the right name! - if test "x$OPENJDK_BUILD_OS" != xaix; then - # FIXME: This test should not be needed anymore; we don't do that for any platform. - { $as_echo "$as_me:${as_lineno-$LINENO}: checking resolved symbolic links for CXX" >&5 + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking resolved symbolic links for CXX" >&5 $as_echo_n "checking resolved symbolic links for CXX... " >&6; } + SYMLINK_ORIGINAL="$TEST_COMPILER" if test "x$OPENJDK_BUILD_OS" != xwindows; then # Follow a chain of symbolic links. Use readlink @@ -33794,13 +33327,13 @@ $as_echo_n "checking resolved symbolic links for CXX... " >&6; } fi if test "x$READLINK" != x; then - TEST_COMPILER=`$READLINK -f $TEST_COMPILER` + SYMLINK_ORIGINAL=`$READLINK -f $SYMLINK_ORIGINAL` else # Save the current directory for restoring afterwards STARTDIR=$PWD COUNTER=0 - sym_link_dir=`$DIRNAME $TEST_COMPILER` - sym_link_file=`$BASENAME $TEST_COMPILER` + sym_link_dir=`$DIRNAME $SYMLINK_ORIGINAL` + sym_link_file=`$BASENAME $SYMLINK_ORIGINAL` cd $sym_link_dir # Use -P flag to resolve symlinks in directories. cd `$THEPWDCMD -P` @@ -33820,474 +33353,25 @@ $as_echo_n "checking resolved symbolic links for CXX... " >&6; } let COUNTER=COUNTER+1 done cd $STARTDIR - TEST_COMPILER=$sym_link_dir/$sym_link_file + SYMLINK_ORIGINAL=$sym_link_dir/$sym_link_file fi fi - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $TEST_COMPILER" >&5 -$as_echo "$TEST_COMPILER" >&6; } - fi - { $as_echo "$as_me:${as_lineno-$LINENO}: checking if CXX is disguised ccache" >&5 -$as_echo_n "checking if CXX is disguised ccache... " >&6; } - - COMPILER_BASENAME=`$BASENAME "$TEST_COMPILER"` - if test "x$COMPILER_BASENAME" = "xccache"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes, trying to find proper $COMPILER_NAME compiler" >&5 -$as_echo "yes, trying to find proper $COMPILER_NAME compiler" >&6; } - # We /usr/lib/ccache in the path, so cc is a symlink to /usr/bin/ccache. - # We want to control ccache invocation ourselves, so ignore this cc and try - # searching again. - - # Remove the path to the fake ccache cc from the PATH - RETRY_COMPILER_SAVED_PATH="$PATH" - COMPILER_DIRNAME=`$DIRNAME $CXX` - PATH="`$ECHO $PATH | $SED -e "s,$COMPILER_DIRNAME,,g" -e "s,::,:,g" -e "s,^:,,g"`" - - # Try again looking for our compiler - if test -n "$ac_tool_prefix"; then - for ac_prog in $TOOLCHAIN_CXX_BINARY - do - # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. -set dummy $ac_tool_prefix$ac_prog; ac_word=$2 -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -$as_echo_n "checking for $ac_word... " >&6; } -if ${ac_cv_prog_PROPER_COMPILER_CXX+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test -n "$PROPER_COMPILER_CXX"; then - ac_cv_prog_PROPER_COMPILER_CXX="$PROPER_COMPILER_CXX" # Let the user override the test. -else -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then - ac_cv_prog_PROPER_COMPILER_CXX="$ac_tool_prefix$ac_prog" - $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - -fi -fi -PROPER_COMPILER_CXX=$ac_cv_prog_PROPER_COMPILER_CXX -if test -n "$PROPER_COMPILER_CXX"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PROPER_COMPILER_CXX" >&5 -$as_echo "$PROPER_COMPILER_CXX" >&6; } -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } -fi - - - test -n "$PROPER_COMPILER_CXX" && break - done -fi -if test -z "$PROPER_COMPILER_CXX"; then - ac_ct_PROPER_COMPILER_CXX=$PROPER_COMPILER_CXX - for ac_prog in $TOOLCHAIN_CXX_BINARY -do - # Extract the first word of "$ac_prog", so it can be a program name with args. -set dummy $ac_prog; ac_word=$2 -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -$as_echo_n "checking for $ac_word... " >&6; } -if ${ac_cv_prog_ac_ct_PROPER_COMPILER_CXX+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test -n "$ac_ct_PROPER_COMPILER_CXX"; then - ac_cv_prog_ac_ct_PROPER_COMPILER_CXX="$ac_ct_PROPER_COMPILER_CXX" # Let the user override the test. -else -as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then - ac_cv_prog_ac_ct_PROPER_COMPILER_CXX="$ac_prog" - $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 - break 2 - fi -done - done -IFS=$as_save_IFS - -fi -fi -ac_ct_PROPER_COMPILER_CXX=$ac_cv_prog_ac_ct_PROPER_COMPILER_CXX -if test -n "$ac_ct_PROPER_COMPILER_CXX"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_PROPER_COMPILER_CXX" >&5 -$as_echo "$ac_ct_PROPER_COMPILER_CXX" >&6; } -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } -fi - - - test -n "$ac_ct_PROPER_COMPILER_CXX" && break -done - - if test "x$ac_ct_PROPER_COMPILER_CXX" = x; then - PROPER_COMPILER_CXX="" + if test "x$TEST_COMPILER" = "x$SYMLINK_ORIGINAL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no symlink" >&5 +$as_echo "no symlink" >&6; } else - case $cross_compiling:$ac_tool_warned in -yes:) -{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 -$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} -ac_tool_warned=yes ;; -esac - PROPER_COMPILER_CXX=$ac_ct_PROPER_COMPILER_CXX - fi -fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SYMLINK_ORIGINAL" >&5 +$as_echo "$SYMLINK_ORIGINAL" >&6; } - - # Only process if variable expands to non-empty - - if test "x$PROPER_COMPILER_CXX" != x; then - if test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.cygwin"; then - - # First separate the path from the arguments. This will split at the first - # space. - complete="$PROPER_COMPILER_CXX" - path="${complete%% *}" - tmp="$complete EOL" - arguments="${tmp#* }" - - # Input might be given as Windows format, start by converting to - # unix format. - new_path=`$CYGPATH -u "$path"` - - # Now try to locate executable using which - new_path=`$WHICH "$new_path" 2> /dev/null` - # bat and cmd files are not always considered executable in cygwin causing which - # to not find them - if test "x$new_path" = x \ - && test "x`$ECHO \"$path\" | $GREP -i -e \"\\.bat$\" -e \"\\.cmd$\"`" != x \ - && test "x`$LS \"$path\" 2>/dev/null`" != x; then - new_path=`$CYGPATH -u "$path"` - fi - if test "x$new_path" = x; then - # Oops. Which didn't find the executable. - # The splitting of arguments from the executable at a space might have been incorrect, - # since paths with space are more likely in Windows. Give it another try with the whole - # argument. - path="$complete" - arguments="EOL" - new_path=`$CYGPATH -u "$path"` - new_path=`$WHICH "$new_path" 2> /dev/null` - # bat and cmd files are not always considered executable in cygwin causing which - # to not find them - if test "x$new_path" = x \ - && test "x`$ECHO \"$path\" | $GREP -i -e \"\\.bat$\" -e \"\\.cmd$\"`" != x \ - && test "x`$LS \"$path\" 2>/dev/null`" != x; then - new_path=`$CYGPATH -u "$path"` + # We can't handle ccache by gcc wrappers, since we need to know if we're + # using ccache. Instead ccache usage must be controlled by a configure option. + COMPILER_BASENAME=`$BASENAME "$SYMLINK_ORIGINAL"` + if test "x$COMPILER_BASENAME" = "xccache"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: Please use --enable-ccache instead of providing a wrapped compiler." >&5 +$as_echo "$as_me: Please use --enable-ccache instead of providing a wrapped compiler." >&6;} + as_fn_error $? "$TEST_COMPILER is a symbolic link to ccache. This is not supported." "$LINENO" 5 fi - if test "x$new_path" = x; then - # It's still not found. Now this is an unrecoverable error. - { $as_echo "$as_me:${as_lineno-$LINENO}: The path of PROPER_COMPILER_CXX, which resolves as \"$complete\", is not found." >&5 -$as_echo "$as_me: The path of PROPER_COMPILER_CXX, which resolves as \"$complete\", is not found." >&6;} - has_space=`$ECHO "$complete" | $GREP " "` - if test "x$has_space" != x; then - { $as_echo "$as_me:${as_lineno-$LINENO}: You might be mixing spaces in the path and extra arguments, which is not allowed." >&5 -$as_echo "$as_me: You might be mixing spaces in the path and extra arguments, which is not allowed." >&6;} - fi - as_fn_error $? "Cannot locate the the path of PROPER_COMPILER_CXX" "$LINENO" 5 - fi - fi - - # Cygwin tries to hide some aspects of the Windows file system, such that binaries are - # named .exe but called without that suffix. Therefore, "foo" and "foo.exe" are considered - # the same file, most of the time (as in "test -f"). But not when running cygpath -s, then - # "foo.exe" is OK but "foo" is an error. - # - # This test is therefore slightly more accurate than "test -f" to check for file presence. - # It is also a way to make sure we got the proper file name for the real test later on. - test_shortpath=`$CYGPATH -s -m "$new_path" 2> /dev/null` - if test "x$test_shortpath" = x; then - # Short path failed, file does not exist as specified. - # Try adding .exe or .cmd - if test -f "${new_path}.exe"; then - input_to_shortpath="${new_path}.exe" - elif test -f "${new_path}.cmd"; then - input_to_shortpath="${new_path}.cmd" - else - { $as_echo "$as_me:${as_lineno-$LINENO}: The path of PROPER_COMPILER_CXX, which resolves as \"$new_path\", is invalid." >&5 -$as_echo "$as_me: The path of PROPER_COMPILER_CXX, which resolves as \"$new_path\", is invalid." >&6;} - { $as_echo "$as_me:${as_lineno-$LINENO}: Neither \"$new_path\" nor \"$new_path.exe/cmd\" can be found" >&5 -$as_echo "$as_me: Neither \"$new_path\" nor \"$new_path.exe/cmd\" can be found" >&6;} - as_fn_error $? "Cannot locate the the path of PROPER_COMPILER_CXX" "$LINENO" 5 - fi - else - input_to_shortpath="$new_path" - fi - - # Call helper function which possibly converts this using DOS-style short mode. - # If so, the updated path is stored in $new_path. - new_path="$input_to_shortpath" - - input_path="$input_to_shortpath" - # Check if we need to convert this using DOS-style short mode. If the path - # contains just simple characters, use it. Otherwise (spaces, weird characters), - # take no chances and rewrite it. - # Note: m4 eats our [], so we need to use [ and ] instead. - has_forbidden_chars=`$ECHO "$input_path" | $GREP [^-._/a-zA-Z0-9]` - if test "x$has_forbidden_chars" != x; then - # Now convert it to mixed DOS-style, short mode (no spaces, and / instead of \) - shortmode_path=`$CYGPATH -s -m -a "$input_path"` - path_after_shortmode=`$CYGPATH -u "$shortmode_path"` - if test "x$path_after_shortmode" != "x$input_to_shortpath"; then - # Going to short mode and back again did indeed matter. Since short mode is - # case insensitive, let's make it lowercase to improve readability. - shortmode_path=`$ECHO "$shortmode_path" | $TR 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` - # Now convert it back to Unix-style (cygpath) - input_path=`$CYGPATH -u "$shortmode_path"` - new_path="$input_path" - fi - fi - - test_cygdrive_prefix=`$ECHO $input_path | $GREP ^/cygdrive/` - if test "x$test_cygdrive_prefix" = x; then - # As a simple fix, exclude /usr/bin since it's not a real path. - if test "x`$ECHO $input_to_shortpath | $GREP ^/usr/bin/`" = x; then - # The path is in a Cygwin special directory (e.g. /home). We need this converted to - # a path prefixed by /cygdrive for fixpath to work. - new_path="$CYGWIN_ROOT_PATH$input_path" - fi - fi - - # remove trailing .exe if any - new_path="${new_path/%.exe/}" - - elif test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.msys"; then - - # First separate the path from the arguments. This will split at the first - # space. - complete="$PROPER_COMPILER_CXX" - path="${complete%% *}" - tmp="$complete EOL" - arguments="${tmp#* }" - - # Input might be given as Windows format, start by converting to - # unix format. - new_path="$path" - - windows_path="$new_path" - if test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.cygwin"; then - unix_path=`$CYGPATH -u "$windows_path"` - new_path="$unix_path" - elif test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.msys"; then - unix_path=`$ECHO "$windows_path" | $SED -e 's,^\\(.\\):,/\\1,g' -e 's,\\\\,/,g'` - new_path="$unix_path" - fi - - - # Now try to locate executable using which - new_path=`$WHICH "$new_path" 2> /dev/null` - - if test "x$new_path" = x; then - # Oops. Which didn't find the executable. - # The splitting of arguments from the executable at a space might have been incorrect, - # since paths with space are more likely in Windows. Give it another try with the whole - # argument. - path="$complete" - arguments="EOL" - new_path="$path" - - windows_path="$new_path" - if test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.cygwin"; then - unix_path=`$CYGPATH -u "$windows_path"` - new_path="$unix_path" - elif test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.msys"; then - unix_path=`$ECHO "$windows_path" | $SED -e 's,^\\(.\\):,/\\1,g' -e 's,\\\\,/,g'` - new_path="$unix_path" - fi - - - new_path=`$WHICH "$new_path" 2> /dev/null` - # bat and cmd files are not always considered executable in MSYS causing which - # to not find them - if test "x$new_path" = x \ - && test "x`$ECHO \"$path\" | $GREP -i -e \"\\.bat$\" -e \"\\.cmd$\"`" != x \ - && test "x`$LS \"$path\" 2>/dev/null`" != x; then - new_path="$path" - - windows_path="$new_path" - if test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.cygwin"; then - unix_path=`$CYGPATH -u "$windows_path"` - new_path="$unix_path" - elif test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.msys"; then - unix_path=`$ECHO "$windows_path" | $SED -e 's,^\\(.\\):,/\\1,g' -e 's,\\\\,/,g'` - new_path="$unix_path" - fi - - fi - - if test "x$new_path" = x; then - # It's still not found. Now this is an unrecoverable error. - { $as_echo "$as_me:${as_lineno-$LINENO}: The path of PROPER_COMPILER_CXX, which resolves as \"$complete\", is not found." >&5 -$as_echo "$as_me: The path of PROPER_COMPILER_CXX, which resolves as \"$complete\", is not found." >&6;} - has_space=`$ECHO "$complete" | $GREP " "` - if test "x$has_space" != x; then - { $as_echo "$as_me:${as_lineno-$LINENO}: You might be mixing spaces in the path and extra arguments, which is not allowed." >&5 -$as_echo "$as_me: You might be mixing spaces in the path and extra arguments, which is not allowed." >&6;} - fi - as_fn_error $? "Cannot locate the the path of PROPER_COMPILER_CXX" "$LINENO" 5 - fi - fi - - # Now new_path has a complete unix path to the binary - if test "x`$ECHO $new_path | $GREP ^/bin/`" != x; then - # Keep paths in /bin as-is, but remove trailing .exe if any - new_path="${new_path/%.exe/}" - # Do not save /bin paths to all_fixpath_prefixes! - else - # Not in mixed or Windows style, start by that. - new_path=`cmd //c echo $new_path` - - input_path="$new_path" - # Check if we need to convert this using DOS-style short mode. If the path - # contains just simple characters, use it. Otherwise (spaces, weird characters), - # take no chances and rewrite it. - # Note: m4 eats our [], so we need to use [ and ] instead. - has_forbidden_chars=`$ECHO "$input_path" | $GREP [^-_/:a-zA-Z0-9]` - if test "x$has_forbidden_chars" != x; then - # Now convert it to mixed DOS-style, short mode (no spaces, and / instead of \) - new_path=`cmd /c "for %A in (\"$input_path\") do @echo %~sA"|$TR \\\\\\\\ / | $TR 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` - fi - - # Output is in $new_path - - windows_path="$new_path" - if test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.cygwin"; then - unix_path=`$CYGPATH -u "$windows_path"` - new_path="$unix_path" - elif test "x$OPENJDK_BUILD_OS_ENV" = "xwindows.msys"; then - unix_path=`$ECHO "$windows_path" | $SED -e 's,^\\(.\\):,/\\1,g' -e 's,\\\\,/,g'` - new_path="$unix_path" - fi - - # remove trailing .exe if any - new_path="${new_path/%.exe/}" - - # Save the first 10 bytes of this path to the storage, so fixpath can work. - all_fixpath_prefixes=("${all_fixpath_prefixes[@]}" "${new_path:0:10}") - fi - - else - # We're on a unix platform. Hooray! :) - # First separate the path from the arguments. This will split at the first - # space. - complete="$PROPER_COMPILER_CXX" - path="${complete%% *}" - tmp="$complete EOL" - arguments="${tmp#* }" - - # Cannot rely on the command "which" here since it doesn't always work. - is_absolute_path=`$ECHO "$path" | $GREP ^/` - if test -z "$is_absolute_path"; then - # Path to executable is not absolute. Find it. - IFS_save="$IFS" - IFS=: - for p in $PATH; do - if test -f "$p/$path" && test -x "$p/$path"; then - new_path="$p/$path" - break - fi - done - IFS="$IFS_save" - else - # This is an absolute path, we can use it without further modifications. - new_path="$path" - fi - - if test "x$new_path" = x; then - { $as_echo "$as_me:${as_lineno-$LINENO}: The path of PROPER_COMPILER_CXX, which resolves as \"$complete\", is not found." >&5 -$as_echo "$as_me: The path of PROPER_COMPILER_CXX, which resolves as \"$complete\", is not found." >&6;} - has_space=`$ECHO "$complete" | $GREP " "` - if test "x$has_space" != x; then - { $as_echo "$as_me:${as_lineno-$LINENO}: This might be caused by spaces in the path, which is not allowed." >&5 -$as_echo "$as_me: This might be caused by spaces in the path, which is not allowed." >&6;} - fi - as_fn_error $? "Cannot locate the the path of PROPER_COMPILER_CXX" "$LINENO" 5 - fi - fi - - # Now join together the path and the arguments once again - if test "x$arguments" != xEOL; then - new_complete="$new_path ${arguments% *}" - else - new_complete="$new_path" - fi - - if test "x$complete" != "x$new_complete"; then - PROPER_COMPILER_CXX="$new_complete" - { $as_echo "$as_me:${as_lineno-$LINENO}: Rewriting PROPER_COMPILER_CXX to \"$new_complete\"" >&5 -$as_echo "$as_me: Rewriting PROPER_COMPILER_CXX to \"$new_complete\"" >&6;} - fi - fi - - PATH="$RETRY_COMPILER_SAVED_PATH" - - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for resolved symbolic links for CXX" >&5 -$as_echo_n "checking for resolved symbolic links for CXX... " >&6; } - - if test "x$OPENJDK_BUILD_OS" != xwindows; then - # Follow a chain of symbolic links. Use readlink - # where it exists, else fall back to horribly - # complicated shell code. - if test "x$READLINK_TESTED" != yes; then - # On MacOSX there is a readlink tool with a different - # purpose than the GNU readlink tool. Check the found readlink. - ISGNU=`$READLINK --version 2>&1 | $GREP GNU` - if test "x$ISGNU" = x; then - # A readlink that we do not know how to use. - # Are there other non-GNU readlinks out there? - READLINK_TESTED=yes - READLINK= - fi - fi - - if test "x$READLINK" != x; then - PROPER_COMPILER_CXX=`$READLINK -f $PROPER_COMPILER_CXX` - else - # Save the current directory for restoring afterwards - STARTDIR=$PWD - COUNTER=0 - sym_link_dir=`$DIRNAME $PROPER_COMPILER_CXX` - sym_link_file=`$BASENAME $PROPER_COMPILER_CXX` - cd $sym_link_dir - # Use -P flag to resolve symlinks in directories. - cd `$THEPWDCMD -P` - sym_link_dir=`$THEPWDCMD -P` - # Resolve file symlinks - while test $COUNTER -lt 20; do - ISLINK=`$LS -l $sym_link_dir/$sym_link_file | $GREP '\->' | $SED -e 's/.*-> \(.*\)/\1/'` - if test "x$ISLINK" == x; then - # This is not a symbolic link! We are done! - break - fi - # Again resolve directory symlinks since the target of the just found - # link could be in a different directory - cd `$DIRNAME $ISLINK` - sym_link_dir=`$THEPWDCMD -P` - sym_link_file=`$BASENAME $ISLINK` - let COUNTER=COUNTER+1 - done - cd $STARTDIR - PROPER_COMPILER_CXX=$sym_link_dir/$sym_link_file - fi - fi - - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PROPER_COMPILER_CXX" >&5 -$as_echo "$PROPER_COMPILER_CXX" >&6; } - CXX="$PROPER_COMPILER_CXX" - else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no, keeping CXX" >&5 -$as_echo "no, keeping CXX" >&6; } fi @@ -58558,6 +57642,10 @@ fi fi done + # Due to https://llvm.org/bugs/show_bug.cgi?id=16902, llvm does not + # always properly detect -ltinfo + LLVM_LIBS="${LLVM_LIBS} -ltinfo" + diff --git a/common/autoconf/hotspot.m4 b/common/autoconf/hotspot.m4 index e6849af3af4..ec357f99572 100644 --- a/common/autoconf/hotspot.m4 +++ b/common/autoconf/hotspot.m4 @@ -266,14 +266,3 @@ AC_DEFUN_ONCE([HOTSPOT_SETUP_BUILD_TWEAKS], HOTSPOT_MAKE_ARGS="$HOTSPOT_TARGET" AC_SUBST(HOTSPOT_MAKE_ARGS) ]) - - # -g is already added by ENABLE_DEBUG_SYMBOLS and the hotspot makefiles - # will basically do slowdebug builds when DEBUG_BINARIES is set for - # fastdebug builds - DEBUG_BINARIES=false - # Fastdebug builds with this setting will essentially be slowdebug - # in hotspot. - # -g is already added by ENABLE_DEBUG_SYMBOLS and the hotspot makefiles - # will basically do slowdebug builds when DEBUG_BINARIES is set for - # fastdebug builds - DEBUG_BINARIES=false \ No newline at end of file diff --git a/common/autoconf/libraries.m4 b/common/autoconf/libraries.m4 index cb3cddd7f92..7bae405555e 100644 --- a/common/autoconf/libraries.m4 +++ b/common/autoconf/libraries.m4 @@ -144,6 +144,10 @@ AC_DEFUN_ONCE([LIB_SETUP_LLVM], fi done + # Due to https://llvm.org/bugs/show_bug.cgi?id=16902, llvm does not + # always properly detect -ltinfo + LLVM_LIBS="${LLVM_LIBS} -ltinfo" + AC_SUBST(LLVM_CFLAGS) AC_SUBST(LLVM_LDFLAGS) AC_SUBST(LLVM_LIBS) diff --git a/common/autoconf/spec.gmk.in b/common/autoconf/spec.gmk.in index e2a73d20ccf..a78b30ba5ad 100644 --- a/common/autoconf/spec.gmk.in +++ b/common/autoconf/spec.gmk.in @@ -500,7 +500,7 @@ JAVAC_FLAGS?=@JAVAC_FLAGS@ INTERIM_LANGTOOLS_JAR = $(BUILDTOOLS_OUTPUTDIR)/interim_langtools.jar INTERIM_LANGTOOLS_ARGS = "-Xbootclasspath/p:$(INTERIM_LANGTOOLS_JAR)" -cp $(INTERIM_LANGTOOLS_JAR) NEW_JAVAC = $(INTERIM_LANGTOOLS_ARGS) com.sun.tools.javac.Main -NEW_JAVADOC = $(INTERIM_LANGTOOLS_ARGS) com.sun.tools.javadoc.Main +NEW_JAVADOC = $(INTERIM_LANGTOOLS_ARGS) jdk.javadoc.internal.tool.Main # Base flags for RC # Guarding this against resetting value. Legacy make files include spec multiple diff --git a/common/autoconf/toolchain.m4 b/common/autoconf/toolchain.m4 index 4254d86a4fb..5741294f309 100644 --- a/common/autoconf/toolchain.m4 +++ b/common/autoconf/toolchain.m4 @@ -433,39 +433,22 @@ AC_DEFUN([TOOLCHAIN_FIND_COMPILER], # Now we have a compiler binary in $1. Make sure it's okay. BASIC_FIXUP_EXECUTABLE($1) TEST_COMPILER="[$]$1" - # Don't remove symbolic links on AIX because 'xlc_r' and 'xlC_r' may all be links - # to 'xlc' but it is crucial that we invoke the compiler with the right name! - if test "x$OPENJDK_BUILD_OS" != xaix; then - # FIXME: This test should not be needed anymore; we don't do that for any platform. - AC_MSG_CHECKING([resolved symbolic links for $1]) - BASIC_REMOVE_SYMBOLIC_LINKS(TEST_COMPILER) - AC_MSG_RESULT([$TEST_COMPILER]) - fi - AC_MSG_CHECKING([if $1 is disguised ccache]) - COMPILER_BASENAME=`$BASENAME "$TEST_COMPILER"` - if test "x$COMPILER_BASENAME" = "xccache"; then - AC_MSG_RESULT([yes, trying to find proper $COMPILER_NAME compiler]) - # We /usr/lib/ccache in the path, so cc is a symlink to /usr/bin/ccache. - # We want to control ccache invocation ourselves, so ignore this cc and try - # searching again. - - # Remove the path to the fake ccache cc from the PATH - RETRY_COMPILER_SAVED_PATH="$PATH" - COMPILER_DIRNAME=`$DIRNAME [$]$1` - PATH="`$ECHO $PATH | $SED -e "s,$COMPILER_DIRNAME,,g" -e "s,::,:,g" -e "s,^:,,g"`" - - # Try again looking for our compiler - AC_CHECK_TOOLS(PROPER_COMPILER_$1, $3) - BASIC_FIXUP_EXECUTABLE(PROPER_COMPILER_$1) - PATH="$RETRY_COMPILER_SAVED_PATH" - - AC_MSG_CHECKING([for resolved symbolic links for $1]) - BASIC_REMOVE_SYMBOLIC_LINKS(PROPER_COMPILER_$1) - AC_MSG_RESULT([$PROPER_COMPILER_$1]) - $1="$PROPER_COMPILER_$1" + AC_MSG_CHECKING([resolved symbolic links for $1]) + SYMLINK_ORIGINAL="$TEST_COMPILER" + BASIC_REMOVE_SYMBOLIC_LINKS(SYMLINK_ORIGINAL) + if test "x$TEST_COMPILER" = "x$SYMLINK_ORIGINAL"; then + AC_MSG_RESULT([no symlink]) else - AC_MSG_RESULT([no, keeping $1]) + AC_MSG_RESULT([$SYMLINK_ORIGINAL]) + + # We can't handle ccache by gcc wrappers, since we need to know if we're + # using ccache. Instead ccache usage must be controlled by a configure option. + COMPILER_BASENAME=`$BASENAME "$SYMLINK_ORIGINAL"` + if test "x$COMPILER_BASENAME" = "xccache"; then + AC_MSG_NOTICE([Please use --enable-ccache instead of providing a wrapped compiler.]) + AC_MSG_ERROR([$TEST_COMPILER is a symbolic link to ccache. This is not supported.]) + fi fi TOOLCHAIN_CHECK_COMPILER_VERSION([$1], [$COMPILER_NAME]) diff --git a/common/bin/compare.sh b/common/bin/compare.sh index 9e680f08b0a..b417e8ed819 100644 --- a/common/bin/compare.sh +++ b/common/bin/compare.sh @@ -306,7 +306,7 @@ compare_general_files() { ! -name "*.lib" ! -name "*.war" ! -name "JavaControlPanel" \ ! -name "*.obj" ! -name "*.o" ! -name "JavaControlPanelHelper" \ ! -name "JavaUpdater" ! -name "JavaWSApplicationStub" \ - ! -name "jspawnhelper" \ + ! -name "jspawnhelper" ! -name "*.a" \ | $GREP -v "./bin/" | $SORT | $FILTER) echo Other files with binary differences... @@ -939,7 +939,7 @@ compare_all_libs() { WORK_DIR=$3 LIBS=$(cd $THIS_DIR && $FIND . -type f \( -name 'lib*.so' -o -name '*.dylib' \ - -o -name '*.dll' -o -name '*.obj' -o -name '*.o' \ + -o -name '*.dll' -o -name '*.obj' -o -name '*.o' -o -name '*.a' \ -o -name '*.cpl' \) | $SORT | $FILTER) if [ -n "$LIBS" ]; then diff --git a/corba/.hgtags b/corba/.hgtags index d486a0a69bb..ba693c7dd28 100644 --- a/corba/.hgtags +++ b/corba/.hgtags @@ -345,3 +345,4 @@ ea285530245cf4e0edf0479121a41347d3030eba jdk-9+98 791d0d3ac0138faeb6110bd840a4545bc1950df2 jdk-9+100 30dfb3bd3d06b4bb80a087babc0d1841edba187b jdk-9+101 9c4662334d933d299928d1f599d02ff50777cbf8 jdk-9+102 +0680fb7dae4da1ee6cf783c4b74184e3e08d3179 jdk-9+103 diff --git a/hotspot/.hgtags b/hotspot/.hgtags index 4731b6143c4..fbc7ac926fb 100644 --- a/hotspot/.hgtags +++ b/hotspot/.hgtags @@ -505,3 +505,4 @@ f008e8cc10d5b3212fb22d58c96fa01d38654f19 jdk-9+99 bdb0acafc63c42e84d9d8195bf2e2b25ee9c3306 jdk-9+100 9f45d3d57d6948cf526fbc2e2891a9a74ac6941a jdk-9+101 d5239fc1b69749ae50793c61b899fcdacf3df857 jdk-9+102 +c5f55130b1b69510d9a6f4a3105b58e21cd7ffe1 jdk-9+103 diff --git a/hotspot/test/gc/metaspace/TestPerfCountersAndMemoryPools.java b/hotspot/test/gc/metaspace/TestPerfCountersAndMemoryPools.java index c04f8ad9377..5d7d3852f43 100644 --- a/hotspot/test/gc/metaspace/TestPerfCountersAndMemoryPools.java +++ b/hotspot/test/gc/metaspace/TestPerfCountersAndMemoryPools.java @@ -64,14 +64,18 @@ public class TestPerfCountersAndMemoryPools { throws Exception { MemoryPoolMXBean pool = getMemoryPool(memoryPoolName); + // First, call all the methods to let them allocate their own slab of metadata + getMinCapacity(perfNS); + getCapacity(perfNS); + getUsed(perfNS); + pool.getUsage().getInit(); + pool.getUsage().getUsed(); + pool.getUsage().getCommitted(); + assertEQ(1L, 1L); + // Must do a GC to update performance counters System.gc(); assertEQ(getMinCapacity(perfNS), pool.getUsage().getInit()); - - // Must do a second GC to update the perfomance counters again, since - // the call pool.getUsage().getInit() could have allocated some - // metadata. - System.gc(); assertEQ(getUsed(perfNS), pool.getUsage().getUsed()); assertEQ(getCapacity(perfNS), pool.getUsage().getCommitted()); } diff --git a/jaxp/.hgtags b/jaxp/.hgtags index 06990f8df41..75132348c9c 100644 --- a/jaxp/.hgtags +++ b/jaxp/.hgtags @@ -345,3 +345,4 @@ c8d0845877a811ab4350935892f826929359a3ff jdk-9+95 d45bcd374f6057851e3c2dcd45607cd362afadfa jdk-9+100 d3e834ff74e724a2b92a558e18e8cbf81c6dbc59 jdk-9+101 9dcf193c0b6cf22c0e89e2dc705a2c0f520ae064 jdk-9+102 +bdbf2342b21bd8ecad1b4e6499a0dfb314952bd7 jdk-9+103 diff --git a/jaxws/.hgtags b/jaxws/.hgtags index 25d05bf7a7f..9909ccb59aa 100644 --- a/jaxws/.hgtags +++ b/jaxws/.hgtags @@ -348,3 +348,4 @@ b55cebc47555293cf9c2aefb3bf63c56e847ab19 jdk-9+96 d0a97e57d2336238edf6a4cd60aafe67deb7258d jdk-9+100 3e99318616da903e0dc8f07f9f9203dc1bd49921 jdk-9+101 0868b93587cc99df3a4f4d3817a1aa756bea60ab jdk-9+102 +eb5e005a17e50d7d8340daaf21a5c3c5ae358d68 jdk-9+103 diff --git a/jaxws/src/java.xml.bind/share/classes/javax/xml/bind/ContextFinder.java b/jaxws/src/java.xml.bind/share/classes/javax/xml/bind/ContextFinder.java index a0329cf7744..b6d9f4d2dc9 100644 --- a/jaxws/src/java.xml.bind/share/classes/javax/xml/bind/ContextFinder.java +++ b/jaxws/src/java.xml.bind/share/classes/javax/xml/bind/ContextFinder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,11 +29,12 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; @@ -105,9 +106,9 @@ class ContextFinder { /** * If the {@link InvocationTargetException} wraps an exception that shouldn't be wrapped, - * throw the wrapped exception. + * throw the wrapped exception. Otherwise returns exception to be wrapped for further processing. */ - private static void handleInvocationTargetException(InvocationTargetException x) throws JAXBException { + private static Throwable handleInvocationTargetException(InvocationTargetException x) throws JAXBException { Throwable t = x.getTargetException(); if (t != null) { if (t instanceof JAXBException) @@ -118,7 +119,9 @@ class ContextFinder { throw (RuntimeException) t; if (t instanceof Error) throw (Error) t; + return t; } + return x; } @@ -157,9 +160,10 @@ class ContextFinder { } catch (ClassNotFoundException x) { throw new JAXBException(Messages.format(Messages.PROVIDER_NOT_FOUND, className), x); - } catch (RuntimeException x) { + } catch (RuntimeException | JAXBException x) { // avoid wrapping RuntimeException to JAXBException, // because it indicates a bug in this code. + // JAXBException re-thrown as is throw x; } catch (Exception x) { // can't catch JAXBException because the method is hidden behind @@ -189,8 +193,9 @@ class ContextFinder { try { Method m = spFactory.getMethod("createContext", String.class, ClassLoader.class, Map.class); // any failure in invoking this method would be considered fatal - context = m.invoke(null, contextPath, classLoader, properties); - } catch (NoSuchMethodException e) { + Object obj = instantiateProviderIfNecessary(m); + context = m.invoke(obj, contextPath, classLoader, properties); + } catch (NoSuchMethodException ignored) { // it's not an error for the provider not to have this method. } @@ -198,8 +203,9 @@ class ContextFinder { // try the old method that doesn't take properties. compatible with 1.0. // it is an error for an implementation not to have both forms of the createContext method. Method m = spFactory.getMethod("createContext", String.class, ClassLoader.class); + Object obj = instantiateProviderIfNecessary(m); // any failure in invoking this method would be considered fatal - context = m.invoke(null, contextPath, classLoader); + context = m.invoke(obj, contextPath, classLoader); } if (!(context instanceof JAXBContext)) { @@ -208,18 +214,11 @@ class ContextFinder { } return (JAXBContext) context; } catch (InvocationTargetException x) { - handleInvocationTargetException(x); - // for other exceptions, wrap the internal target exception - // with a JAXBException - Throwable e = x; - if (x.getTargetException() != null) - e = x.getTargetException(); - + // throw if it is exception not to be wrapped + // otherwise, wrap with a JAXBException + Throwable e = handleInvocationTargetException(x); throw new JAXBException(Messages.format(Messages.COULD_NOT_INSTANTIATE, spFactory, e), e); - } catch (RuntimeException x) { - // avoid wrapping RuntimeException to JAXBException, - // because it indicates a bug in this code. - throw x; + } catch (Exception x) { // can't catch JAXBException because the method is hidden behind // reflection. Root element collisions detected in the call to @@ -229,6 +228,23 @@ class ContextFinder { } } + private static Object instantiateProviderIfNecessary(Method m) throws JAXBException { + Class declaringClass = m.getDeclaringClass(); + try { + if (JAXBContextFactory.class.isAssignableFrom(declaringClass)) { + return AccessController.doPrivileged(new PrivilegedExceptionAction() { + @Override + public Object run() throws Exception { + return declaringClass.newInstance(); + } + }); + } + return null; + } catch (PrivilegedActionException e) { + throw new JAXBException(Messages.format(Messages.COULD_NOT_INSTANTIATE, declaringClass, e), e); + } + } + /** * Create an instance of a class using the thread context ClassLoader */ @@ -255,7 +271,8 @@ class ContextFinder { try { Method m = spFactory.getMethod("createContext", Class[].class, Map.class); - Object context = m.invoke(null, classes, properties); + Object obj = instantiateProviderIfNecessary(m); + Object context = m.invoke(obj, classes, properties); if (!(context instanceof JAXBContext)) { // the cast would fail, so generate an exception with a nice message throw handleClassCastException(context.getClass(), JAXBContext.class); @@ -264,13 +281,10 @@ class ContextFinder { } catch (NoSuchMethodException | IllegalAccessException e) { throw new JAXBException(e); - } catch (InvocationTargetException e) { - handleInvocationTargetException(e); - - Throwable x = e; - if (e.getTargetException() != null) - x = e.getTargetException(); + // throw if it is exception not to be wrapped + // otherwise, wrap with a JAXBException + Throwable x = handleInvocationTargetException(e); throw new JAXBException(x); } diff --git a/jaxws/src/java.xml.bind/share/classes/javax/xml/bind/JAXBContext.java b/jaxws/src/java.xml.bind/share/classes/javax/xml/bind/JAXBContext.java index c5185236400..adfb479a849 100644 --- a/jaxws/src/java.xml.bind/share/classes/javax/xml/bind/JAXBContext.java +++ b/jaxws/src/java.xml.bind/share/classes/javax/xml/bind/JAXBContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -94,7 +94,7 @@ import java.io.InputStream; * allows the merging of global elements and type definitions across a set of schemas (listed * in the {@code contextPath}). Since each schema in the schema set can belong * to distinct namespaces, the unification of schemas to an unmarshalling - * context should be namespace independent. This means that a client + * context must be namespace independent. This means that a client * application is able to unmarshal XML documents that are instances of * any of the schemas listed in the {@code contextPath}. For example: * @@ -200,21 +200,28 @@ import java.io.InputStream; * *

Discovery of JAXB implementation

*

- * When one of the {@code newInstance} methods is called, a JAXB implementation is discovered - * by the following steps. + * To create an instance of {@link JAXBContext}, one of {@code JAXBContext.newInstance(...)} methods is invoked. After + * JAX-B implementation is discovered, call is delegated to appropriate provider's method {@code createContext(...)} + * passing parameters from the original call. + *

+ * JAX-B implementation discovery happens each time {@code JAXBContext.newInstance} is invoked. If there is no user + * specific configuration provided, default JAX-B provider must be returned. + *

+ * Implementation discovery consists of following steps: * *

    * *
  1. - * For each package/class explicitly passed in to the {@link #newInstance} method, in the order they are specified, - * {@code jaxb.properties} file is looked up in its package, by using the associated classloader — + * Packages/classes explicitly passed in to the {@link #newInstance} method are processed in the order they are + * specified, until {@code jaxb.properties} file is looked up in its package, by using the associated classloader — * this is {@link Class#getClassLoader() the owner class loader} for a {@link Class} argument, and for a package * the specified {@link ClassLoader}. * *

    - * If such a file is discovered, it is {@link Properties#load(InputStream) loaded} as a property file, and - * the value of the {@link #JAXB_CONTEXT_FACTORY} key will be assumed to be the provider factory class. - * This class is then loaded by the associated class loader discussed above. + * If such a resource is discovered, it is {@link Properties#load(InputStream) loaded} as a property file, and + * the value of the {@link #JAXB_CONTEXT_FACTORY} key will be assumed to be the provider factory class. If no value + * found, {@code "javax.xml.bind.context.factory"} is used as a key for backwards compatibility reasons. This class is + * then loaded by the associated class loader discussed above. * *

    * This phase of the look up allows some packages to force the use of a certain JAXB implementation. @@ -222,7 +229,9 @@ import java.io.InputStream; * *

  2. * If the system property {@link #JAXB_CONTEXT_FACTORY} exists, then its value is assumed to be the provider - * factory class. This phase of the look up enables per-JVM override of the JAXB implementation. + * factory class. If no such property exists, properties {@code "javax.xml.bind.context.factory"} and + * {@code "javax.xml.bind.JAXBContext"} are checked too (in this order), for backwards compatibility reasons. This phase + * of the look up enables per-JVM override of the JAXB implementation. * *
  3. * Provider of {@link javax.xml.bind.JAXBContextFactory} is loaded using the service-provider loading @@ -235,43 +244,58 @@ import java.io.InputStream; *
    * In case of {@link java.util.ServiceConfigurationError service * configuration error} a {@link javax.xml.bind.JAXBException} will be thrown. - *
  4. * *
  5. * Look for resource {@code /META-INF/services/javax.xml.bind.JAXBContext} using provided class loader. * Methods without class loader parameter use {@code Thread.currentThread().getContextClassLoader()}. - * If such a resource exists, its content is assumed to be the provider factory class and must supply - * an implementation class containing the following method signatures: + * If such a resource exists, its content is assumed to be the provider factory class. * - *
    - *
    - * public static JAXBContext createContext(
    - *                                      String contextPath,
    - *                                      ClassLoader classLoader,
    - *                                      Map<String,Object> properties throws JAXBException
    - *
    - * public static JAXBContext createContext(
    - *                                      Class[] classes,
    - *                                      Map<String,Object> properties ) throws JAXBException
    - * 
    * This configuration method is deprecated. * *
  6. * Finally, if all the steps above fail, then the rest of the look up is unspecified. That said, * the recommended behavior is to simply look for some hard-coded platform default JAXB implementation. - * This phase of the look up is so that JavaSE can have its own JAXB implementation as the last resort. + * This phase of the look up is so that Java SE can have its own JAXB implementation as the last resort. *
* *

- * Once the provider factory class {@link javax.xml.bind.JAXBContextFactory} is discovered, one of its methods - * {@link javax.xml.bind.JAXBContextFactory#createContext(String, ClassLoader, java.util.Map)} or - * {@link javax.xml.bind.JAXBContextFactory#createContext(Class[], java.util.Map)} is invoked - * to create a {@link JAXBContext}. + * Once the provider factory class is discovered, context creation is delegated to one of its + * {@code createContext(...)} methods. + * + * For backward compatibility reasons, there are two ways how to implement provider factory class: + *

    + *
  1. the class is implementation of {@link javax.xml.bind.JAXBContextFactory}. It must also implement no-arg + * constructor. If discovered in other step then 3, new instance using no-arg constructor is created first. + * After that, appropriate instance method is invoked on this instance. + *
  2. the class is not implementation of interface above and then it is mandated to implement the following + * static method signatures: + *
    + *
    + * public static JAXBContext createContext(
    + *                                      String contextPath,
    + *                                      ClassLoader classLoader,
    + *                                      Map<String,Object> properties ) throws JAXBException
    + *
    + * public static JAXBContext createContext(
    + *                                      Class[] classes,
    + *                                      Map<String,Object> properties ) throws JAXBException
    + * 
    + * In this scenario, appropriate static method is used instead of instance method. This approach is incompatible + * with {@link java.util.ServiceLoader} so it can't be used with step 3. + *
+ *

+ * There is no difference in behavior of given method {@code createContext(...)} regardless of whether it uses approach + * 1 (JAXBContextFactory) or 2 (no interface, static methods). * * @apiNote - *

Service discovery method using file /META-INF/services/javax.xml.bind.JAXBContext (described in step 4) - * and leveraging provider's static methods is supported only to allow backwards compatibility, but it is strongly - * recommended to migrate to standard ServiceLoader mechanism (described in step 3). + * Service discovery method using resource {@code /META-INF/services/javax.xml.bind.JAXBContext} (described in step 4) + * is supported only to allow backwards compatibility, it is strongly recommended to migrate to standard + * {@link java.util.ServiceLoader} mechanism (described in step 3). The difference here is the resource name, which + * doesn't match service's type name. + *

+ * Also using providers implementing interface {@link JAXBContextFactory} is preferred over using ones defining + * static methods, same as {@link JAXBContext#JAXB_CONTEXT_FACTORY} property is preferred over property + * {@code "javax.xml.bind.context.factory"} * * @implNote * Within the last step, if Glassfish AS environment detected, its specific service loader is used to find factory class. @@ -308,16 +332,10 @@ public abstract class JAXBContext { * the context class loader of the current thread. * * @throws JAXBException if an error was encountered while creating the - * {@code JAXBContext} such as - *

    - *
  1. failure to locate either ObjectFactory.class or jaxb.index in the packages
  2. - *
  3. an ambiguity among global elements contained in the contextPath
  4. - *
  5. failure to locate a value for the context factory provider property
  6. - *
  7. mixing schema derived packages from different providers on the same contextPath
  8. - *
+ * {@code JAXBContext}. See {@link JAXBContext#newInstance(String, ClassLoader, Map)} for details. */ public static JAXBContext newInstance( String contextPath ) - throws JAXBException { + throws JAXBException { //return newInstance( contextPath, JAXBContext.class.getClassLoader() ); return newInstance( contextPath, getContextClassLoader()); @@ -405,13 +423,7 @@ public abstract class JAXBContext { * * @return a new instance of a {@code JAXBContext} * @throws JAXBException if an error was encountered while creating the - * {@code JAXBContext} such as - *
    - *
  1. failure to locate either ObjectFactory.class or jaxb.index in the packages
  2. - *
  3. an ambiguity among global elements contained in the contextPath
  4. - *
  5. failure to locate a value for the context factory provider property
  6. - *
  7. mixing schema derived packages from different providers on the same contextPath
  8. - *
+ * {@code JAXBContext}. See {@link JAXBContext#newInstance(String, ClassLoader, Map)} for details. */ public static JAXBContext newInstance( String contextPath, ClassLoader classLoader ) throws JAXBException { @@ -427,7 +439,7 @@ public abstract class JAXBContext { * the instantiation of {@link JAXBContext}. * *

- * The interpretation of properties is up to implementations. Implementations should + * The interpretation of properties is up to implementations. Implementations must * throw {@code JAXBException} if it finds properties that it doesn't understand. * * @param contextPath list of java package names that contain schema derived classes @@ -439,13 +451,7 @@ public abstract class JAXBContext { * * @return a new instance of a {@code JAXBContext} * @throws JAXBException if an error was encountered while creating the - * {@code JAXBContext} such as - *

    - *
  1. failure to locate either ObjectFactory.class or jaxb.index in the packages
  2. - *
  3. an ambiguity among global elements contained in the contextPath
  4. - *
  5. failure to locate a value for the context factory provider property
  6. - *
  7. mixing schema derived packages from different providers on the same contextPath
  8. - *
+ * {@code JAXBContext}. See {@link #newInstance(String, ClassLoader)} for details. * @since 1.6, JAXB 2.0 */ public static JAXBContext newInstance( String contextPath, @@ -454,14 +460,14 @@ public abstract class JAXBContext { return ContextFinder.find( /* The default property name according to the JAXB spec */ - JAXB_CONTEXT_FACTORY, + JAXB_CONTEXT_FACTORY, /* the context path supplied by the client app */ - contextPath, + contextPath, /* class loader to be used */ - classLoader, - properties ); + classLoader, + properties ); } // TODO: resurrect this once we introduce external annotations @@ -583,17 +589,8 @@ public abstract class JAXBContext { * @return * A new instance of a {@code JAXBContext}. * - * @throws JAXBException - * if an error was encountered while creating the - * {@code JAXBContext}, such as (but not limited to): - *
    - *
  1. No JAXB implementation was discovered - *
  2. Classes use JAXB annotations incorrectly - *
  3. Classes have colliding annotations (i.e., two classes with the same type name) - *
  4. The JAXB implementation was unable to locate - * provider-specific out-of-band information (such as additional - * files generated at the development time.) - *
+ * @throws JAXBException if an error was encountered while creating the + * {@code JAXBContext}. See {@link JAXBContext#newInstance(Class[], Map)} for details. * * @throws IllegalArgumentException * if the parameter contains {@code null} (i.e., {@code newInstance(null);}) @@ -601,7 +598,7 @@ public abstract class JAXBContext { * @since 1.6, JAXB 2.0 */ public static JAXBContext newInstance( Class ... classesToBeBound ) - throws JAXBException { + throws JAXBException { return newInstance(classesToBeBound,Collections.emptyMap()); } @@ -614,7 +611,7 @@ public abstract class JAXBContext { * to configure 'properties' for this instantiation of {@link JAXBContext}. * *

- * The interpretation of properties is up to implementations. Implementations should + * The interpretation of properties is up to implementations. Implementations must * throw {@code JAXBException} if it finds properties that it doesn't understand. * * @param classesToBeBound @@ -646,10 +643,10 @@ public abstract class JAXBContext { * @since 1.6, JAXB 2.0 */ public static JAXBContext newInstance( Class[] classesToBeBound, Map properties ) - throws JAXBException { + throws JAXBException { if (classesToBeBound == null) { - throw new IllegalArgumentException(); + throw new IllegalArgumentException(); } // but it is an error to have nulls in it. diff --git a/jaxws/src/java.xml.bind/share/classes/javax/xml/bind/JAXBContextFactory.java b/jaxws/src/java.xml.bind/share/classes/javax/xml/bind/JAXBContextFactory.java index 09630484dc9..ef98dc61e4f 100644 --- a/jaxws/src/java.xml.bind/share/classes/javax/xml/bind/JAXBContextFactory.java +++ b/jaxws/src/java.xml.bind/share/classes/javax/xml/bind/JAXBContextFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -56,14 +56,7 @@ public interface JAXBContextFactory { * * @throws JAXBException * if an error was encountered while creating the - * {@code JAXBContext}, such as (but not limited to): - *

    - *
  1. Classes use JAXB annotations incorrectly - *
  2. Classes have colliding annotations (i.e., two classes with the same type name) - *
  3. The JAXB implementation was unable to locate - * provider-specific out-of-band information (such as additional - * files generated at the development time.) - *
+ * {@code JAXBContext}. See {@link JAXBContext#newInstance(Class[], Map)} for details. * * @throws IllegalArgumentException * if the parameter contains {@code null} (i.e., {@code newInstance(null,someMap);}) @@ -81,7 +74,7 @@ public interface JAXBContextFactory { * For semantics see {@link javax.xml.bind.JAXBContext#newInstance(String, ClassLoader, java.util.Map)} * *

- * The interpretation of properties is up to implementations. Implementations should + * The interpretation of properties is up to implementations. Implementations must * throw {@code JAXBException} if it finds properties that it doesn't understand. * * @param contextPath list of java package names that contain schema derived classes @@ -93,13 +86,8 @@ public interface JAXBContextFactory { * * @return a new instance of a {@code JAXBContext} * @throws JAXBException if an error was encountered while creating the - * {@code JAXBContext} such as - *

    - *
  1. failure to locate either ObjectFactory.class or jaxb.index in the packages
  2. - *
  3. an ambiguity among global elements contained in the contextPath
  4. - *
  5. failure to locate a value for the context factory provider property
  6. - *
  7. mixing schema derived packages from different providers on the same contextPath
  8. - *
+ * {@code JAXBContext}. See {@link JAXBContext#newInstance(String, ClassLoader, Map)} for details. + * * @since 9, JAXB 2.3 */ JAXBContext createContext(String contextPath, diff --git a/jdk/src/java.base/share/classes/java/lang/invoke/StringConcatException.java b/jdk/src/java.base/share/classes/java/lang/invoke/StringConcatException.java index f6bc7e9bd87..1cf0a66e370 100644 --- a/jdk/src/java.base/share/classes/java/lang/invoke/StringConcatException.java +++ b/jdk/src/java.base/share/classes/java/lang/invoke/StringConcatException.java @@ -26,6 +26,8 @@ package java.lang.invoke; /** * StringConcatException is thrown by {@link StringConcatFactory} when linkage * invariants are violated. + * + * @since 9 */ public class StringConcatException extends Exception { private static final long serialVersionUID = 292L + 9L; diff --git a/jdk/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java b/jdk/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java index 6b0cfb8289c..016c939a285 100644 --- a/jdk/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java +++ b/jdk/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java @@ -96,6 +96,8 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*; * more than 200 argument slots. Users requiring more than 200 argument slots in * concatenation are expected to split the large concatenation in smaller * expressions. + * + * @since 9 */ public final class StringConcatFactory { diff --git a/jdk/src/java.base/share/classes/java/net/URI.java b/jdk/src/java.base/share/classes/java/net/URI.java index 862588019fd..069aedb685f 100644 --- a/jdk/src/java.base/share/classes/java/net/URI.java +++ b/jdk/src/java.base/share/classes/java/net/URI.java @@ -1080,11 +1080,8 @@ public final class URI * If a protocol handler for the URL could not be found, * or if some other error occurred while constructing the URL */ - public URL toURL() - throws MalformedURLException { - if (!isAbsolute()) - throw new IllegalArgumentException("URI is not absolute"); - return new URL(toString()); + public URL toURL() throws MalformedURLException { + return URL.fromURI(this); } // -- Component access methods -- diff --git a/jdk/src/java.base/share/classes/java/net/URL.java b/jdk/src/java.base/share/classes/java/net/URL.java index 8891a47dbdd..598e4103cf7 100644 --- a/jdk/src/java.base/share/classes/java/net/URL.java +++ b/jdk/src/java.base/share/classes/java/net/URL.java @@ -659,6 +659,44 @@ public final class URL implements java.io.Serializable { } } + /** + * Creates a URL from a URI, as if by invoking {@code uri.toURL()}. + * + * @see java.net.URI#toURL() + */ + static URL fromURI(URI uri) throws MalformedURLException { + if (!uri.isAbsolute()) { + throw new IllegalArgumentException("URI is not absolute"); + } + String protocol = uri.getScheme(); + + // In general we need to go via Handler.parseURL, but for the jrt + // protocol we enforce that the Handler is not overrideable and can + // optimize URI to URL conversion. + // + // Case-sensitive comparison for performance; malformed protocols will + // be handled correctly by the slow path. + if (protocol.equals("jrt") && !uri.isOpaque() + && uri.getRawFragment() == null) { + + String query = uri.getRawQuery(); + String path = uri.getRawPath(); + String file = (query == null) ? path : path + "?" + query; + + // URL represent undefined host as empty string while URI use null + String host = uri.getHost(); + if (host == null) { + host = ""; + } + + int port = uri.getPort(); + + return new URL("jrt", host, port, file, null); + } else { + return new URL((URL)null, uri.toString(), null); + } + } + /* * Returns true if specified string is a valid protocol name. */ @@ -1275,11 +1313,28 @@ public final class URL implements java.io.Serializable { } } - private static final String[] NON_OVERRIDEABLE_PROTOCOLS = {"file", "jrt"}; - private static boolean isOverrideable(String protocol) { - for (String p : NON_OVERRIDEABLE_PROTOCOLS) - if (protocol.equalsIgnoreCase(p)) + + /** + * Non-overrideable protocols: "jrt" and "file" + * + * Character-based comparison for performance reasons; also ensures + * case-insensitive comparison in a locale-independent fashion. + */ + static boolean isOverrideable(String protocol) { + if (protocol.length() == 3) { + if ((Character.toLowerCase(protocol.charAt(0)) == 'j') && + (Character.toLowerCase(protocol.charAt(1)) == 'r') && + (Character.toLowerCase(protocol.charAt(2)) == 't')) { return false; + } + } else if (protocol.length() == 4) { + if ((Character.toLowerCase(protocol.charAt(0)) == 'f') && + (Character.toLowerCase(protocol.charAt(1)) == 'i') && + (Character.toLowerCase(protocol.charAt(2)) == 'l') && + (Character.toLowerCase(protocol.charAt(3)) == 'e')) { + return false; + } + } return true; } diff --git a/jdk/src/java.base/share/classes/java/time/LocalDate.java b/jdk/src/java.base/share/classes/java/time/LocalDate.java index 3d8c034fa91..940f21f0f1e 100644 --- a/jdk/src/java.base/share/classes/java/time/LocalDate.java +++ b/jdk/src/java.base/share/classes/java/time/LocalDate.java @@ -100,6 +100,8 @@ import java.time.temporal.ValueRange; import java.time.zone.ZoneOffsetTransition; import java.time.zone.ZoneRules; import java.util.Objects; +import java.util.stream.LongStream; +import java.util.stream.Stream; /** * A date without a time-zone in the ISO-8601 calendar system, @@ -1715,6 +1717,89 @@ public final class LocalDate return Period.of(Math.toIntExact(years), months, days); } + /** + * Returns a sequential ordered stream of dates. The returned stream starts from this date + * (inclusive) and goes to {@code endExclusive} (exclusive) by an incremental step of 1 day. + *

+ * This method is equivalent to {@code datesUntil(endExclusive, Period.ofDays(1))}. + * + * @param endExclusive the end date, exclusive, not null + * @return a sequential {@code Stream} for the range of {@code LocalDate} values + * @throws IllegalArgumentException if end date is before this date + * @since 9 + */ + public Stream datesUntil(LocalDate endExclusive) { + long end = endExclusive.toEpochDay(); + long start = toEpochDay(); + if (end < start) { + throw new IllegalArgumentException(endExclusive + " < " + this); + } + return LongStream.range(start, end).mapToObj(LocalDate::ofEpochDay); + } + + /** + * Returns a sequential ordered stream of dates by given incremental step. The returned stream + * starts from this date (inclusive) and goes to {@code endExclusive} (exclusive). + *

+ * The n-th date which appears in the stream is equal to {@code this.plus(step.multipliedBy(n))} + * (but the result of step multiplication never overflows). For example, if this date is + * {@code 2015-01-31}, the end date is {@code 2015-05-01} and the step is 1 month, then the + * stream contains {@code 2015-01-31}, {@code 2015-02-28}, {@code 2015-03-31}, and + * {@code 2015-04-30}. + * + * @param endExclusive the end date, exclusive, not null + * @param step the non-zero, non-negative {@code Period} which represents the step. + * @return a sequential {@code Stream} for the range of {@code LocalDate} values + * @throws IllegalArgumentException if step is zero, or {@code step.getDays()} and + * {@code step.toTotalMonths()} have opposite sign, or end date is before this date + * and step is positive, or end date is after this date and step is negative + * @since 9 + */ + public Stream datesUntil(LocalDate endExclusive, Period step) { + if (step.isZero()) { + throw new IllegalArgumentException("step is zero"); + } + long end = endExclusive.toEpochDay(); + long start = toEpochDay(); + long until = end - start; + long months = step.toTotalMonths(); + long days = step.getDays(); + if ((months < 0 && days > 0) || (months > 0 && days < 0)) { + throw new IllegalArgumentException("period months and days are of opposite sign"); + } + if (until == 0) { + return Stream.empty(); + } + int sign = months > 0 || days > 0 ? 1 : -1; + if (sign < 0 ^ until < 0) { + throw new IllegalArgumentException(endExclusive + (sign < 0 ? " > " : " < ") + this); + } + if (months == 0) { + long steps = (until - sign) / days; // non-negative + return LongStream.rangeClosed(0, steps).mapToObj( + n -> LocalDate.ofEpochDay(start + n * days)); + } + // 48699/1600 = 365.2425/12, no overflow, non-negative result + long steps = until * 1600 / (months * 48699 + days * 1600) + 1; + long addMonths = months * steps; + long addDays = days * steps; + long maxAddMonths = months > 0 ? MAX.getProlepticMonth() - getProlepticMonth() + : getProlepticMonth() - MIN.getProlepticMonth(); + // adjust steps estimation + if (addMonths * sign > maxAddMonths + || (plusMonths(addMonths).toEpochDay() + addDays) * sign >= end * sign) { + steps--; + addMonths -= months; + addDays -= days; + if (addMonths * sign > maxAddMonths + || (plusMonths(addMonths).toEpochDay() + addDays) * sign >= end * sign) { + steps--; + } + } + return LongStream.rangeClosed(0, steps).mapToObj( + n -> this.plusMonths(months * n).plusDays(days * n)); + } + /** * Formats this date using the specified formatter. *

diff --git a/jdk/src/java.base/share/classes/java/util/Map.java b/jdk/src/java.base/share/classes/java/util/Map.java index 0967523eba3..615bb0a5344 100644 --- a/jdk/src/java.base/share/classes/java/util/Map.java +++ b/jdk/src/java.base/share/classes/java/util/Map.java @@ -649,7 +649,7 @@ public interface Map { try { k = entry.getKey(); v = entry.getValue(); - } catch(IllegalStateException ise) { + } catch (IllegalStateException ise) { // this usually means the entry is no longer in the map. throw new ConcurrentModificationException(ise); } @@ -704,7 +704,7 @@ public interface Map { try { k = entry.getKey(); v = entry.getValue(); - } catch(IllegalStateException ise) { + } catch (IllegalStateException ise) { // this usually means the entry is no longer in the map. throw new ConcurrentModificationException(ise); } @@ -714,7 +714,7 @@ public interface Map { try { entry.setValue(v); - } catch(IllegalStateException ise) { + } catch (IllegalStateException ise) { // this usually means the entry is no longer in the map. throw new ConcurrentModificationException(ise); } @@ -887,7 +887,7 @@ public interface Map { * or atomicity properties of this method. Any implementation providing * atomicity guarantees must override this method and document its * concurrency properties. - * + * * @param key key with which the specified value is associated * @param value value to be associated with the specified key * @return the previous value associated with the specified key, or @@ -984,6 +984,9 @@ public interface Map { * @throws ClassCastException if the class of the specified key or value * prevents it from being stored in this map * (optional) + * @throws IllegalArgumentException if some property of the specified key + * or value prevents it from being stored in this map + * (optional) * @since 1.8 */ default V computeIfAbsent(K key, @@ -1058,6 +1061,9 @@ public interface Map { * @throws ClassCastException if the class of the specified key or value * prevents it from being stored in this map * (optional) + * @throws IllegalArgumentException if some property of the specified key + * or value prevents it from being stored in this map + * (optional) * @since 1.8 */ default V computeIfPresent(K key, @@ -1103,7 +1109,7 @@ public interface Map { *

 {@code
      * V oldValue = map.get(key);
      * V newValue = remappingFunction.apply(key, oldValue);
-     * if (oldValue != null ) {
+     * if (oldValue != null) {
      *    if (newValue != null)
      *       map.put(key, newValue);
      *    else
@@ -1147,6 +1153,9 @@ public interface Map {
      * @throws ClassCastException if the class of the specified key or value
      *         prevents it from being stored in this map
      *         (optional)
+     * @throws IllegalArgumentException if some property of the specified key
+     *         or value prevents it from being stored in this map
+     *         (optional)
      * @since 1.8
      */
     default V compute(K key,
@@ -1239,6 +1248,9 @@ public interface Map {
      * @throws ClassCastException if the class of the specified key or value
      *         prevents it from being stored in this map
      *         (optional)
+     * @throws IllegalArgumentException if some property of the specified key
+     *         or value prevents it from being stored in this map
+     *         (optional)
      * @throws NullPointerException if the specified key is null and this map
      *         does not support null keys or the value or remappingFunction is
      *         null
@@ -1251,7 +1263,7 @@ public interface Map {
         V oldValue = get(key);
         V newValue = (oldValue == null) ? value :
                    remappingFunction.apply(oldValue, value);
-        if(newValue == null) {
+        if (newValue == null) {
             remove(key);
         } else {
             put(key, newValue);
diff --git a/jdk/src/java.base/share/classes/java/util/Queue.java b/jdk/src/java.base/share/classes/java/util/Queue.java
index b5456b99f2f..7d5e39c7030 100644
--- a/jdk/src/java.base/share/classes/java/util/Queue.java
+++ b/jdk/src/java.base/share/classes/java/util/Queue.java
@@ -129,14 +129,6 @@ package java.util;
  * 
  * Java Collections Framework.
  *
- * @see java.util.Collection
- * @see LinkedList
- * @see PriorityQueue
- * @see java.util.concurrent.LinkedBlockingQueue
- * @see java.util.concurrent.BlockingQueue
- * @see java.util.concurrent.ArrayBlockingQueue
- * @see java.util.concurrent.LinkedBlockingQueue
- * @see java.util.concurrent.PriorityBlockingQueue
  * @since 1.5
  * @author Doug Lea
  * @param  the type of elements held in this queue
diff --git a/jdk/src/java.base/share/classes/java/util/concurrent/ConcurrentMap.java b/jdk/src/java.base/share/classes/java/util/concurrent/ConcurrentMap.java
index 37b97c36265..d301de9eee6 100644
--- a/jdk/src/java.base/share/classes/java/util/concurrent/ConcurrentMap.java
+++ b/jdk/src/java.base/share/classes/java/util/concurrent/ConcurrentMap.java
@@ -301,19 +301,15 @@ public interface ConcurrentMap extends Map {
      *
      * @implSpec
      * The default implementation is equivalent to the following steps for this
-     * {@code map}, then returning the current value or {@code null} if now
-     * absent:
+     * {@code map}:
      *
      * 
 {@code
-     * if (map.get(key) == null) {
-     *   V newValue = mappingFunction.apply(key);
-     *   if (newValue != null)
-     *     return map.putIfAbsent(key, newValue);
-     * }}
- * - * The default implementation may retry these steps when multiple - * threads attempt updates including potentially calling the mapping - * function multiple times. + * V oldValue, newValue; + * return ((oldValue = map.get(key)) == null + * && (newValue = mappingFunction.apply(key)) != null + * && (oldValue = map.putIfAbsent(key, newValue)) == null) + * ? newValue + * : oldValue;}
* *

This implementation assumes that the ConcurrentMap cannot contain null * values and {@code get()} returning null unambiguously means the key is @@ -323,16 +319,19 @@ public interface ConcurrentMap extends Map { * @throws UnsupportedOperationException {@inheritDoc} * @throws ClassCastException {@inheritDoc} * @throws NullPointerException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} * @since 1.8 */ @Override default V computeIfAbsent(K key, Function mappingFunction) { Objects.requireNonNull(mappingFunction); - V v, newValue; - return ((v = get(key)) == null && - (newValue = mappingFunction.apply(key)) != null && - (v = putIfAbsent(key, newValue)) == null) ? newValue : v; + V oldValue, newValue; + return ((oldValue = get(key)) == null + && (newValue = mappingFunction.apply(key)) != null + && (oldValue = putIfAbsent(key, newValue)) == null) + ? newValue + : oldValue; } /** @@ -340,22 +339,19 @@ public interface ConcurrentMap extends Map { * * @implSpec * The default implementation is equivalent to performing the following - * steps for this {@code map}, then returning the current value or - * {@code null} if now absent: + * steps for this {@code map}: * *

 {@code
-     * if (map.get(key) != null) {
-     *   V oldValue = map.get(key);
+     * for (V oldValue; (oldValue = map.get(key)) != null; ) {
      *   V newValue = remappingFunction.apply(key, oldValue);
-     *   if (newValue != null)
-     *     map.replace(key, oldValue, newValue);
-     *   else
-     *     map.remove(key, oldValue);
-     * }}
- * - * The default implementation may retry these steps when multiple threads - * attempt updates including potentially calling the remapping function - * multiple times. + * if ((newValue == null) + * ? map.remove(key, oldValue) + * : map.replace(key, oldValue, newValue)) + * return newValue; + * } + * return null;} + * When multiple threads attempt updates, map operations and the + * remapping function may be called multiple times. * *

This implementation assumes that the ConcurrentMap cannot contain null * values and {@code get()} returning null unambiguously means the key is @@ -365,22 +361,21 @@ public interface ConcurrentMap extends Map { * @throws UnsupportedOperationException {@inheritDoc} * @throws ClassCastException {@inheritDoc} * @throws NullPointerException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} * @since 1.8 */ @Override default V computeIfPresent(K key, BiFunction remappingFunction) { Objects.requireNonNull(remappingFunction); - V oldValue; - while ((oldValue = get(key)) != null) { + for (V oldValue; (oldValue = get(key)) != null; ) { V newValue = remappingFunction.apply(key, oldValue); - if (newValue != null) { - if (replace(key, oldValue, newValue)) - return newValue; - } else if (remove(key, oldValue)) - return null; + if ((newValue == null) + ? remove(key, oldValue) + : replace(key, oldValue, newValue)) + return newValue; } - return oldValue; + return null; } /** @@ -388,27 +383,23 @@ public interface ConcurrentMap extends Map { * * @implSpec * The default implementation is equivalent to performing the following - * steps for this {@code map}, then returning the current value or - * {@code null} if absent: + * steps for this {@code map}: * *

 {@code
-     * V oldValue = map.get(key);
-     * V newValue = remappingFunction.apply(key, oldValue);
-     * if (oldValue != null ) {
-     *   if (newValue != null)
-     *     map.replace(key, oldValue, newValue);
-     *   else
-     *     map.remove(key, oldValue);
-     * } else {
-     *   if (newValue != null)
-     *     map.putIfAbsent(key, newValue);
-     *   else
+     * for (;;) {
+     *   V oldValue = map.get(key);
+     *   V newValue = remappingFunction.apply(key, oldValue);
+     *   if (newValue != null) {
+     *     if ((oldValue != null)
+     *       ? map.replace(key, oldValue, newValue)
+     *       : map.putIfAbsent(key, newValue) == null)
+     *       return newValue;
+     *   } else if (oldValue == null || map.remove(key, oldValue)) {
      *     return null;
+     *   }
      * }}
- * - * The default implementation may retry these steps when multiple - * threads attempt updates including potentially calling the remapping - * function multiple times. + * When multiple threads attempt updates, map operations and the + * remapping function may be called multiple times. * *

This implementation assumes that the ConcurrentMap cannot contain null * values and {@code get()} returning null unambiguously means the key is @@ -418,50 +409,29 @@ public interface ConcurrentMap extends Map { * @throws UnsupportedOperationException {@inheritDoc} * @throws ClassCastException {@inheritDoc} * @throws NullPointerException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} * @since 1.8 */ @Override default V compute(K key, - BiFunction remappingFunction) { - Objects.requireNonNull(remappingFunction); - V oldValue = get(key); - for (;;) { - V newValue = remappingFunction.apply(key, oldValue); - if (newValue == null) { - // delete mapping - if (oldValue != null || containsKey(key)) { - // something to remove - if (remove(key, oldValue)) { - // removed the old value as expected - return null; + BiFunction remappingFunction) { + retry: for (;;) { + V oldValue = get(key); + // if putIfAbsent fails, opportunistically use its return value + haveOldValue: for (;;) { + V newValue = remappingFunction.apply(key, oldValue); + if (newValue != null) { + if (oldValue != null) { + if (replace(key, oldValue, newValue)) + return newValue; } - - // some other value replaced old value. try again. - oldValue = get(key); - } else { - // nothing to do. Leave things as they were. + else if ((oldValue = putIfAbsent(key, newValue)) == null) + return newValue; + else continue haveOldValue; + } else if (oldValue == null || remove(key, oldValue)) { return null; } - } else { - // add or replace old mapping - if (oldValue != null) { - // replace - if (replace(key, oldValue, newValue)) { - // replaced as expected. - return newValue; - } - - // some other value replaced old value. try again. - oldValue = get(key); - } else { - // add (replace if oldValue was null) - if ((oldValue = putIfAbsent(key, newValue)) == null) { - // replaced - return newValue; - } - - // some other value replaced old value. try again. - } + continue retry; } } } @@ -471,21 +441,25 @@ public interface ConcurrentMap extends Map { * * @implSpec * The default implementation is equivalent to performing the following - * steps for this {@code map}, then returning the current value or - * {@code null} if absent: + * steps for this {@code map}: * *

 {@code
-     * V oldValue = map.get(key);
-     * V newValue = (oldValue == null) ? value :
-     *     remappingFunction.apply(oldValue, value);
-     * if (newValue == null)
-     *   map.remove(key);
-     * else
-     *   map.put(key, newValue);}
- * - *

The default implementation may retry these steps when multiple - * threads attempt updates including potentially calling the remapping - * function multiple times. + * for (;;) { + * V oldValue = map.get(key); + * if (oldValue != null) { + * V newValue = remappingFunction.apply(oldValue, value); + * if (newValue != null) { + * if (map.replace(key, oldValue, newValue)) + * return newValue; + * } else if (map.remove(key, oldValue)) { + * return null; + * } + * } else if (map.putIfAbsent(key, value) == null) { + * return value; + * } + * }} + * When multiple threads attempt updates, map operations and the + * remapping function may be called multiple times. * *

This implementation assumes that the ConcurrentMap cannot contain null * values and {@code get()} returning null unambiguously means the key is @@ -495,6 +469,7 @@ public interface ConcurrentMap extends Map { * @throws UnsupportedOperationException {@inheritDoc} * @throws ClassCastException {@inheritDoc} * @throws NullPointerException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} * @since 1.8 */ @Override @@ -502,20 +477,23 @@ public interface ConcurrentMap extends Map { BiFunction remappingFunction) { Objects.requireNonNull(remappingFunction); Objects.requireNonNull(value); - V oldValue = get(key); - for (;;) { - if (oldValue != null) { - V newValue = remappingFunction.apply(oldValue, value); - if (newValue != null) { - if (replace(key, oldValue, newValue)) - return newValue; - } else if (remove(key, oldValue)) { - return null; - } - oldValue = get(key); - } else { - if ((oldValue = putIfAbsent(key, value)) == null) { - return value; + retry: for (;;) { + V oldValue = get(key); + // if putIfAbsent fails, opportunistically use its return value + haveOldValue: for (;;) { + if (oldValue != null) { + V newValue = remappingFunction.apply(oldValue, value); + if (newValue != null) { + if (replace(key, oldValue, newValue)) + return newValue; + } else if (remove(key, oldValue)) { + return null; + } + continue retry; + } else { + if ((oldValue = putIfAbsent(key, value)) == null) + return value; + continue haveOldValue; } } } diff --git a/jdk/test/java/lang/ref/CleanerTest.java b/jdk/test/java/lang/ref/CleanerTest.java index fedcaf88713..396fbed88bd 100644 --- a/jdk/test/java/lang/ref/CleanerTest.java +++ b/jdk/test/java/lang/ref/CleanerTest.java @@ -41,14 +41,17 @@ import jdk.internal.ref.CleanerFactory; import sun.hotspot.WhiteBox; +import jdk.test.lib.Utils; + import org.testng.Assert; import org.testng.TestNG; import org.testng.annotations.Test; /* * @test - * @library /lib/testlibrary /test/lib + * @library /test/lib/share/classes /lib/testlibrary /test/lib * @build sun.hotspot.WhiteBox + * @build jdk.test.lib.Utils * @modules java.base/jdk.internal.misc java.base/jdk.internal.ref * @run main ClassFileInstaller sun.hotspot.WhiteBox * @run testng/othervm @@ -88,8 +91,7 @@ public class CleanerTest { CleanableCase s = setupPhantom(COMMON, cleaner); cleaner = null; - Assert.assertTrue(checkCleaned(s.getSemaphore()), - "Cleaner cleanup should have occurred"); + checkCleaned(s.getSemaphore(), true, "Cleaner was cleaned:"); } /** @@ -124,8 +126,7 @@ public class CleanerTest { CleanableCase s = setupPhantom(COMMON, cleaner); cleaner = null; - Assert.assertTrue(checkCleaned(s.getSemaphore()), - "Cleaner cleanup should have occurred"); + checkCleaned(s.getSemaphore(), true, "Cleaner was cleaned:"); } /** @@ -213,16 +214,11 @@ public class CleanerTest { CleanableCase cc = setupPhantom(COMMON, test.getCleanable()); test.clearCleanable(); // release this hard reference - boolean result = checkCleaned(test.getSemaphore()); - if (result) { - Assert.assertEquals(r, CleanableCase.EV_CLEAN, - "cleaned; but not expected"); - } else { - Assert.assertNotEquals(r, CleanableCase.EV_CLEAN, - "not cleaned; expected cleaning"); - } - Assert.assertTrue(checkCleaned(cc.getSemaphore()), - "The reference to the Cleanable should have been freed"); + checkCleaned(test.getSemaphore(), + r == CleanableCase.EV_CLEAN, + "Cleanable was cleaned:"); + checkCleaned(cc.getSemaphore(), true, + "The reference to the Cleanable was freed:"); } /** @@ -278,27 +274,32 @@ public class CleanerTest { * Check a semaphore having been released by cleanup handler. * Force a number of GC cycles to give the GC a chance to process * the Reference and for the cleanup action to be run. + * Use a larger number of cycles to wait for an expected cleaning to occur. * * @param semaphore a Semaphore - * @return true if the semaphores has 1 permit, false otherwise. + * @param expectCleaned true if cleaning should occur + * @param msg a message to explain the error */ - static boolean checkCleaned(Semaphore semaphore) { - int cycle = 0; - for (; cycle < 3; cycle++) { + static void checkCleaned(Semaphore semaphore, boolean expectCleaned, + String msg) { + long max_cycles = expectCleaned ? 10 : 3; + long cycle = 0; + for (; cycle < max_cycles; cycle++) { + // Force GC + whitebox.fullGC(); + try { - if (semaphore.tryAcquire(10L, TimeUnit.MILLISECONDS)) { + if (semaphore.tryAcquire(Utils.adjustTimeout(10L), TimeUnit.MILLISECONDS)) { System.out.printf(" Cleanable cleaned in cycle: %d%n", cycle); - return true; + Assert.assertEquals(true, expectCleaned, msg); + return; } } catch (InterruptedException ie) { // retry in outer loop } - // Force GC - whitebox.fullGC(); } // Object has not been cleaned - System.out.printf(" Cleanable not cleaned%n"); - return false; // Failing result + Assert.assertEquals(false, expectCleaned, msg); } /** @@ -622,7 +623,7 @@ public class CleanerTest { System.gc(); Assert.assertNotEquals(map.get(k2), data, "value should not be found in the map"); - final int CYCLE_MAX = 30; + final long CYCLE_MAX = Utils.adjustTimeout(30L); for (int i = 1; map.size() > 0 && i < CYCLE_MAX; i++) { map.forEach( (k, v) -> System.out.printf(" k: %s, v: %s%n", k, v)); try { @@ -699,7 +700,7 @@ public class CleanerTest { } obj = null; - Assert.assertTrue(checkCleaned(s1), "reference should be cleaned;"); + checkCleaned(s1, true, "reference was cleaned:"); cleaner = null; } @@ -713,7 +714,7 @@ public class CleanerTest { Object obj = new Object(); CleanableCase s = setupPhantom(cleaner, obj); obj = null; - Assert.assertTrue(checkCleaned(s.getSemaphore()), - "Object cleaning should have occurred using CleanerFactor.cleaner()"); + checkCleaned(s.getSemaphore(), true, + "Object was cleaned using CleanerFactor.cleaner():"); } } diff --git a/jdk/test/java/net/URI/URItoURLTest.java b/jdk/test/java/net/URI/URItoURLTest.java index e1a5577bb74..05fadebf8f9 100644 --- a/jdk/test/java/net/URI/URItoURLTest.java +++ b/jdk/test/java/net/URI/URItoURLTest.java @@ -23,13 +23,16 @@ /** * @test - * @bug 4768755 4677045 - * @summary URL.equal(URL) is inconsistant for opaque URI.toURL() - * and new URL(URI.toString) + * @bug 4768755 4677045 8147462 + * @summary URL.equal(URL) is inconsistent for opaque URI.toURL() + * and new URL(URI.toString) * URI.toURL() does not always work as specified + * Ensure URIs representing invalid/malformed URLs throw similar + * exception with new URL(URI.toString()) and URI.toURL() */ import java.net.*; +import java.util.Objects; public class URItoURLTest { @@ -39,19 +42,43 @@ public class URItoURLTest { URL classUrl = testClass.getClass(). getResource("/java/lang/Object.class"); - String[] uris = { "mailto:xyz@abc.de", + String[] uris = { + "mailto:xyz@abc.de", "file:xyz#ab", "http:abc/xyz/pqr", + "http:abc/xyz/pqr?id=x%0a&ca=true", "file:/C:/v700/dev/unitTesting/tests/apiUtil/uri", "http:///p", + "file:/C:/v700/dev/unitTesting/tests/apiUtil/uri", + "file:/C:/v700/dev%20src/unitTesting/tests/apiUtil/uri", + "file:/C:/v700/dev%20src/./unitTesting/./tests/apiUtil/uri", + "http://localhost:80/abc/./xyz/../pqr?id=x%0a&ca=true", + "file:./test/./x", + "file:./././%20#i=3", + "file:?hmm", + "file:.#hmm", classUrl.toExternalForm(), }; + // Strings that represent valid URIs but invalid URLs that should throw + // MalformedURLException both when calling toURL and new URL(String) + String[] malformedUrls = { + "test:/test", + "fiel:test", + }; + + // Non-absolute URIs should throw IAE when calling toURL but will throw + // MalformedURLException when calling new URL + String[] illegalUris = { + "./test", + "/test", + }; + boolean isTestFailed = false; boolean isURLFailed = false; - for (int i = 0; i < uris.length; i++) { - URI uri = URI.create(uris[i]); + for (String uriString : uris) { + URI uri = URI.create(uriString); URL url1 = new URL(uri.toString()); URL url2 = uri.toURL(); @@ -107,6 +134,42 @@ public class URItoURLTest { System.out.println(); isURLFailed = false; } + for (String malformedUrl : malformedUrls) { + Exception toURLEx = null; + Exception newURLEx = null; + try { + new URI(malformedUrl).toURL(); + } catch (Exception e) { + // expected + toURLEx = e; + } + try { + new URL(new URI(malformedUrl).toString()); + } catch (Exception e) { + // expected + newURLEx = e; + } + if (!(toURLEx instanceof MalformedURLException) || + !(newURLEx instanceof MalformedURLException) || + !toURLEx.getMessage().equals(newURLEx.getMessage())) { + isTestFailed = true; + System.out.println("Expected the same MalformedURLException: " + + newURLEx + " vs " + toURLEx); + } + } + for (String illegalUri : illegalUris) { + try { + new URI(illegalUri).toURL(); + } catch (IllegalArgumentException e) { + // pass + } + + try { + new URL(illegalUri); + } catch (MalformedURLException e) { + // pass + } + } if (isTestFailed) { throw new Exception("URI.toURL() test failed"); } diff --git a/jdk/test/java/time/tck/java/time/TCKLocalDate.java b/jdk/test/java/time/tck/java/time/TCKLocalDate.java index 96290401c97..2b5832465c9 100644 --- a/jdk/test/java/time/tck/java/time/TCKLocalDate.java +++ b/jdk/test/java/time/tck/java/time/TCKLocalDate.java @@ -119,6 +119,8 @@ import java.time.temporal.UnsupportedTemporalTypeException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; @@ -2385,4 +2387,204 @@ public class TCKLocalDate extends AbstractDateTimeTest { assertSame(isoEra,IsoEra.CE); assertSame(LocalDate.MIN.getEra(),IsoEra.BCE); } + + //----------------------------------------------------------------- + // datesUntil() + // ---------------------------------------------------------------- + @Test + public void test_datesUntil() { + assertEquals( + date(2015, 9, 29).datesUntil(date(2015, 10, 3)).collect( + Collectors.toList()), Arrays.asList(date(2015, 9, 29), + date(2015, 9, 30), date(2015, 10, 1), date(2015, 10, 2))); + assertEquals(date(2015, 9, 29).datesUntil(date(2015, 10, 3), Period.ofDays(2)) + .collect(Collectors.toList()), Arrays.asList(date(2015, 9, 29), + date(2015, 10, 1))); + assertEquals(date(2015, 1, 31).datesUntil(date(2015, 6, 1), Period.ofMonths(1)) + .collect(Collectors.toList()), Arrays.asList(date(2015, 1, 31), + date(2015, 2, 28), date(2015, 3, 31), date(2015, 4, 30), + date(2015, 5, 31))); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_datesUntil_nullEnd() { + LocalDate date = date(2015, 1, 31); + date.datesUntil(null); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_datesUntil_nullEndStep() { + LocalDate date = date(2015, 1, 31); + date.datesUntil(null, Period.ofDays(1)); + } + + @Test(expectedExceptions=NullPointerException.class) + public void test_datesUntil_nullStep() { + LocalDate date = date(2015, 1, 31); + date.datesUntil(date, null); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void test_datesUntil_endBeforeStart() { + date(2015, 1, 31).datesUntil(date(2015, 1, 30)); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void test_datesUntil_endBeforeStartPositiveStep() { + date(2015, 1, 31).datesUntil(date(2015, 1, 30), Period.of(1, 0, 0)); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void test_datesUntil_endAfterStartNegativeStep() { + date(2015, 1, 30).datesUntil(date(2015, 1, 31), Period.of(0, -1, -1)); + } + + @Test(expectedExceptions=IllegalArgumentException.class) + public void test_datesUntil_zeroStep() { + LocalDate date = date(2015, 1, 31); + date.datesUntil(date, Period.ZERO); + } + + @Test(expectedExceptions=IllegalArgumentException.class) + public void test_datesUntil_oppositeSign() { + LocalDate date = date(2015, 1, 31); + date.datesUntil(date, Period.of(1, 0, -1)); + } + + @Test(expectedExceptions=IllegalArgumentException.class) + public void test_datesUntil_oppositeSign2() { + LocalDate date = date(2015, 1, 31); + date.datesUntil(date, Period.of(0, -1, 1)); + } + + @DataProvider(name="datesUntil") + public Object[][] provider_datesUntil() { + return new Object[][] { + {MIN_DATE, MIN_DATE}, + {MIN_DATE, MAX_DATE}, + {MAX_DATE, MAX_DATE}, + {date(2015,10,1), date(2015,10,2)}, + {date(2015,10,1), date(2015,11,1)}, + {date(2015,10,31), date(2015,11,1)}, + {date(2015,10,1), MAX_DATE}, + {MIN_DATE, date(2015,10,1)} + }; + } + + @Test(dataProvider = "datesUntil") + public void test_datesUntil_count(LocalDate start, LocalDate end) { + assertEquals(start.datesUntil(end).count(), start.until(end, ChronoUnit.DAYS)); + assertEquals(start.datesUntil(end, Period.ofDays(1)).count(), + start.until(end, ChronoUnit.DAYS)); + } + + @DataProvider(name="datesUntilSteps") + public Object[][] provider_datesUntil_steps() { + List data = new ArrayList<>(Arrays.asList(new Object[][] { + {MIN_DATE, MAX_DATE, Period.ofYears(Year.MAX_VALUE)}, + {MIN_DATE, MAX_DATE, Period.ofDays(2)}, + {MIN_DATE, MAX_DATE, Period.of(1,2,3)}, + {MIN_DATE, MAX_DATE, Period.of(1,2,1000000)}, + {MIN_DATE, MAX_DATE, Period.of(1,1000000,3)}, + {MIN_DATE, MAX_DATE, Period.of(1000000,2,3)}, + {MIN_DATE, MIN_DATE.plusMonths(1), Period.ofMonths(1)}, + {MIN_DATE, date(Year.MIN_VALUE, 2, 2), Period.ofMonths(1)}, + {MIN_DATE, date(Year.MIN_VALUE, 8, 9), Period.of(0, 1, 1)}, + {MIN_DATE, MAX_DATE.minusYears(1), Period.ofYears(Year.MAX_VALUE)}, + {MAX_DATE.minusMonths(1), MAX_DATE, Period.ofMonths(1)}, + {date(Year.MAX_VALUE, 2, 20), MAX_DATE, Period.of(0, 1, 1)}, + {date(2015,1,1), date(2016,1,1), Period.ofYears(1)}, + {date(2015,1,1), date(2016,1,1), Period.ofDays(365)}, + {date(2015,1,1), date(2016,1,1), Period.ofDays(366)}, + {date(2015,1,1), date(2016,1,1), Period.ofDays(4)}, + {date(2015,1,1), date(2016,1,1), Period.of(0,1,2)}, + {date(2015,1,1), date(2016,1,1), Period.ofMonths(1)}, + {date(2015,1,1), date(2016,1,1), Period.ofMonths(12)}, + {date(2015,1,1), date(2016,1,2), Period.ofMonths(12)}, + {date(2015,1,1), date(2016,1,1), Period.of(0, 11, 30)}, + {date(2015,1,1), date(2015,12,31), Period.of(0, 11, 30)}, + {date(2015,1,31), date(2015,12,31), Period.ofMonths(2)}, + {date(2015,1,31), date(2015,12,1), Period.ofMonths(2)}, + {date(2015,1,31), date(2015,11,30), Period.ofMonths(2)}, + {date(2015,1,31), date(2030,11,30), Period.of(1,30,365)}, + {date(2015,1,31), date(2043,1,31), Period.of(4,0,0)}, + {date(2015,1,31), date(2043,2,1), Period.of(4,0,0)}, + {date(2015,1,31), date(2043,1,31), Period.of(3,11,30)}, + {date(2015,1,31), date(2043,2,1), Period.of(3,11,30)}, + {date(2015,1,31), date(2043,1,31), Period.of(0,0,1460)}, + {date(2015,1,31), date(2043,1,31), Period.of(0,0,1461)}, + {date(2015,1,31), date(2043,2,1), Period.of(0,0,1461)}, + {date(2015,1,31), MAX_DATE, Period.of(10,100,1000)}, + {date(2015,1,31), MAX_DATE, Period.of(1000000,10000,100000)}, + {date(2015,1,31), MAX_DATE, Period.ofDays(10000000)}, + {date(2015,1,31), MAX_DATE, Period.ofDays(Integer.MAX_VALUE)}, + {date(2015,1,31), MAX_DATE, Period.ofMonths(Integer.MAX_VALUE)}, + {date(2015,1,31), MAX_DATE, Period.ofYears(Integer.MAX_VALUE)} + })); + LocalDate start = date(2014, 1, 15); + LocalDate end = date(2015, 3, 4); + for (int months : new int[] { 0, 1, 2, 3, 5, 7, 12, 13 }) { + for (int days : new int[] { 0, 1, 2, 3, 5, 10, 17, 27, 28, 29, 30, 31, 32, 57, 58, 59, + 60, 61, 62, 70, 80, 90 }) { + if (months > 0 || days > 0) + data.add(new Object[] { start, end, Period.of(0, months, days) }); + } + } + for (int days = 27; days < 100; days++) { + data.add(new Object[] { start, start.plusDays(days), Period.ofMonths(1) }); + } + return data.toArray(new Object[data.size()][]); + } + + @Test(dataProvider="datesUntilSteps") + public void test_datesUntil_step(LocalDate start, LocalDate end, Period step) { + assertEquals(start.datesUntil(start, step).count(), 0); + long count = start.datesUntil(end, step).count(); + assertTrue(count > 0); + // the last value must be before the end date + assertTrue(start.plusMonths(step.toTotalMonths()*(count-1)).plusDays(step.getDays()*(count-1)).isBefore(end)); + try { + // the next after the last value must be either invalid or not before the end date + assertFalse(start.plusMonths(step.toTotalMonths()*count).plusDays(step.getDays()*count).isBefore(end)); + } catch (ArithmeticException | DateTimeException e) { + // ignore: possible overflow for the next value is ok + } + if(count < 1000) { + assertTrue(start.datesUntil(end, step).allMatch(date -> !date.isBefore(start) && date.isBefore(end))); + List list = new ArrayList<>(); + for(long i=0; i 0); + // the last value must be after the start date + assertTrue(end.minusMonths(step.toTotalMonths()*(count-1)).minusDays(step.getDays()*(count-1)).isAfter(start)); + try { + // the next after the last value must be either invalid or not after the start date + assertFalse(end.minusMonths(step.toTotalMonths()*count).minusDays(step.getDays()*count).isAfter(start)); + } catch (ArithmeticException | DateTimeException e) { + // ignore: possible overflow for the next value is ok + } + if(count < 1000) { + assertTrue(end.datesUntil(start, step.negated()).allMatch(date -> date.isAfter(start) && !date.isAfter(end))); + List list = new ArrayList<>(); + for(long i=0; i stream = date.datesUntil(date.plusDays(5)); + long sum = stream.mapToInt(LocalDate::getDayOfMonth).sum(); + assertEquals(sum, 60, "sum of 10, 11, 12, 13, 14 is wrong"); + } } diff --git a/jdk/test/java/util/Map/Defaults.java b/jdk/test/java/util/Map/Defaults.java index 37c2fad21dc..7b2956f8846 100644 --- a/jdk/test/java/util/Map/Defaults.java +++ b/jdk/test/java/util/Map/Defaults.java @@ -48,11 +48,14 @@ import java.util.WeakHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiFunction; +import java.util.function.Function; import java.util.function.Supplier; import org.testng.annotations.Test; import org.testng.annotations.DataProvider; +import static java.util.Objects.requireNonNull; import static org.testng.Assert.fail; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @@ -533,6 +536,191 @@ public class Defaults { "Should throw NPE"); } + /** A function that flipflops between running two other functions. */ + static BiFunction twoStep(AtomicBoolean b, + BiFunction first, + BiFunction second) { + return (t, u) -> { + boolean bb = b.get(); + try { + return (b.get() ? first : second).apply(t, u); + } finally { + b.set(!bb); + }}; + } + + /** + * Simulates races by modifying the map within the mapping function. + */ + @Test + public void testConcurrentMap_computeIfAbsent_racy() { + final ConcurrentMap map = new ImplementsConcurrentMap<>(); + final Long two = 2L; + Function f, g; + + // race not detected if function returns null + f = (k) -> { map.put(two, 42L); return null; }; + assertNull(map.computeIfAbsent(two, f)); + assertEquals(42L, (long)map.get(two)); + + map.clear(); + f = (k) -> { map.put(two, 42L); return 86L; }; + assertEquals(42L, (long)map.computeIfAbsent(two, f)); + assertEquals(42L, (long)map.get(two)); + + // mapping function ignored if value already exists + map.put(two, 99L); + assertEquals(99L, (long)map.computeIfAbsent(two, f)); + assertEquals(99L, (long)map.get(two)); + } + + /** + * Simulates races by modifying the map within the remapping function. + */ + @Test + public void testConcurrentMap_computeIfPresent_racy() { + final AtomicBoolean b = new AtomicBoolean(true); + final ConcurrentMap map = new ImplementsConcurrentMap<>(); + final Long two = 2L; + BiFunction f, g; + + for (Long val : new Long[] { null, 86L }) { + map.clear(); + + // Function not invoked if no mapping exists + f = (k, v) -> { map.put(two, 42L); return val; }; + assertNull(map.computeIfPresent(two, f)); + assertNull(map.get(two)); + + map.put(two, 42L); + f = (k, v) -> { map.put(two, 86L); return val; }; + g = (k, v) -> { + assertSame(two, k); + assertEquals(86L, (long)v); + return null; + }; + assertNull(map.computeIfPresent(two, twoStep(b, f, g))); + assertFalse(map.containsKey(two)); + assertTrue(b.get()); + + map.put(two, 42L); + f = (k, v) -> { map.put(two, 86L); return val; }; + g = (k, v) -> { + assertSame(two, k); + assertEquals(86L, (long)v); + return 99L; + }; + assertEquals(99L, (long)map.computeIfPresent(two, twoStep(b, f, g))); + assertTrue(map.containsKey(two)); + assertTrue(b.get()); + } + } + + @Test + public void testConcurrentMap_compute_simple() { + final ConcurrentMap map = new ImplementsConcurrentMap<>(); + BiFunction fun = (k, v) -> ((v == null) ? 0L : k + v); + assertEquals(Long.valueOf(0L), map.compute(3L, fun)); + assertEquals(Long.valueOf(3L), map.compute(3L, fun)); + assertEquals(Long.valueOf(6L), map.compute(3L, fun)); + assertNull(map.compute(3L, (k, v) -> null)); + assertTrue(map.isEmpty()); + + assertEquals(Long.valueOf(0L), map.compute(new Long(3L), fun)); + assertEquals(Long.valueOf(3L), map.compute(new Long(3L), fun)); + assertEquals(Long.valueOf(6L), map.compute(new Long(3L), fun)); + assertNull(map.compute(3L, (k, v) -> null)); + assertTrue(map.isEmpty()); + } + + /** + * Simulates races by modifying the map within the remapping function. + */ + @Test + public void testConcurrentMap_compute_racy() { + final AtomicBoolean b = new AtomicBoolean(true); + final ConcurrentMap map = new ImplementsConcurrentMap<>(); + final Long two = 2L; + BiFunction f, g; + + // null -> null is a no-op; race not detected + f = (k, v) -> { map.put(two, 42L); return null; }; + assertNull(map.compute(two, f)); + assertEquals(42L, (long)map.get(two)); + + for (Long val : new Long[] { null, 86L }) { + map.clear(); + + f = (k, v) -> { map.put(two, 42L); return 86L; }; + g = (k, v) -> { + assertSame(two, k); + assertEquals(42L, (long)v); + return k + v; + }; + assertEquals(44L, (long)map.compute(two, twoStep(b, f, g))); + assertEquals(44L, (long)map.get(two)); + assertTrue(b.get()); + + f = (k, v) -> { map.remove(two); return val; }; + g = (k, v) -> { + assertSame(two, k); + assertNull(v); + return 44L; + }; + assertEquals(44L, (long)map.compute(two, twoStep(b, f, g))); + assertEquals(44L, (long)map.get(two)); + assertTrue(map.containsKey(two)); + assertTrue(b.get()); + + f = (k, v) -> { map.remove(two); return val; }; + g = (k, v) -> { + assertSame(two, k); + assertNull(v); + return null; + }; + assertNull(map.compute(two, twoStep(b, f, g))); + assertNull(map.get(two)); + assertFalse(map.containsKey(two)); + assertTrue(b.get()); + } + } + + /** + * Simulates races by modifying the map within the remapping function. + */ + @Test + public void testConcurrentMap_merge_racy() { + final AtomicBoolean b = new AtomicBoolean(true); + final ConcurrentMap map = new ImplementsConcurrentMap<>(); + final Long two = 2L; + BiFunction f, g; + + for (Long val : new Long[] { null, 86L }) { + map.clear(); + + f = (v, w) -> { throw new AssertionError(); }; + assertEquals(99L, (long)map.merge(two, 99L, f)); + assertEquals(99L, (long)map.get(two)); + + f = (v, w) -> { map.put(two, 42L); return val; }; + g = (v, w) -> { + assertEquals(42L, (long)v); + assertEquals(3L, (long)w); + return v + w; + }; + assertEquals(45L, (long)map.merge(two, 3L, twoStep(b, f, g))); + assertEquals(45L, (long)map.get(two)); + assertTrue(b.get()); + + f = (v, w) -> { map.remove(two); return val; }; + g = (k, v) -> { throw new AssertionError(); }; + assertEquals(55L, (long)map.merge(two, 55L, twoStep(b, f, g))); + assertEquals(55L, (long)map.get(two)); + assertTrue(map.containsKey(two)); + assertFalse(b.get()); b.set(true); + } + } + public enum IntegerEnum { e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, @@ -838,13 +1026,13 @@ public class Defaults { protected ExtendsAbstractMap(M map) { this.map = map; } - public Set> entrySet() { + @Override public Set> entrySet() { return new AbstractSet>() { - public int size() { + @Override public int size() { return map.size(); } - public Iterator> iterator() { + @Override public Iterator> iterator() { final Iterator> source = map.entrySet().iterator(); return new Iterator>() { public boolean hasNext() { return source.hasNext(); } @@ -853,20 +1041,20 @@ public class Defaults { }; } - public boolean add(Map.Entry e) { + @Override public boolean add(Map.Entry e) { return map.entrySet().add(e); } }; } - public V put(K key, V value) { + @Override public V put(K key, V value) { return map.put(key, value); } } /** * A simple mutable concurrent map implementation that provides only default - * implementations of all methods. ie. none of the ConcurrentMap interface + * implementations of all methods, i.e. none of the ConcurrentMap interface * default methods have overridden implementations. * * @param Type of keys @@ -875,14 +1063,26 @@ public class Defaults { public static class ImplementsConcurrentMap extends ExtendsAbstractMap, K, V> implements ConcurrentMap { public ImplementsConcurrentMap() { super(new ConcurrentHashMap()); } - // ConcurrentMap reabstracts these methods + // ConcurrentMap reabstracts these methods. + // + // Unlike ConcurrentHashMap, we have zero tolerance for null values. - public V replace(K k, V v) { return map.replace(k, v); }; + @Override public V replace(K k, V v) { + return map.replace(requireNonNull(k), requireNonNull(v)); + } - public boolean replace(K k, V v, V vv) { return map.replace(k, v, vv); }; + @Override public boolean replace(K k, V v, V vv) { + return map.replace(requireNonNull(k), + requireNonNull(v), + requireNonNull(vv)); + } - public boolean remove(Object k, Object v) { return map.remove(k, v); } + @Override public boolean remove(Object k, Object v) { + return map.remove(requireNonNull(k), requireNonNull(v)); + } - public V putIfAbsent(K k, V v) { return map.putIfAbsent(k, v); } + @Override public V putIfAbsent(K k, V v) { + return map.putIfAbsent(requireNonNull(k), requireNonNull(v)); + } } } diff --git a/jdk/test/java/util/concurrent/tck/AbstractExecutorServiceTest.java b/jdk/test/java/util/concurrent/tck/AbstractExecutorServiceTest.java new file mode 100644 index 00000000000..9e140da9f72 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/AbstractExecutorServiceTest.java @@ -0,0 +1,635 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.security.PrivilegedAction; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.AbstractExecutorService; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AbstractExecutorServiceTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(AbstractExecutorServiceTest.class); + } + + /** + * A no-frills implementation of AbstractExecutorService, designed + * to test the submit methods only. + */ + static class DirectExecutorService extends AbstractExecutorService { + public void execute(Runnable r) { r.run(); } + public void shutdown() { shutdown = true; } + public List shutdownNow() { + shutdown = true; + return Collections.EMPTY_LIST; + } + public boolean isShutdown() { return shutdown; } + public boolean isTerminated() { return isShutdown(); } + public boolean awaitTermination(long timeout, TimeUnit unit) { + return isShutdown(); + } + private volatile boolean shutdown = false; + } + + /** + * execute(runnable) runs it to completion + */ + public void testExecuteRunnable() throws Exception { + ExecutorService e = new DirectExecutorService(); + final AtomicBoolean done = new AtomicBoolean(false); + Future future = e.submit(new CheckedRunnable() { + public void realRun() { + done.set(true); + }}); + assertNull(future.get()); + assertNull(future.get(0, MILLISECONDS)); + assertTrue(done.get()); + assertTrue(future.isDone()); + assertFalse(future.isCancelled()); + } + + /** + * Completed submit(callable) returns result + */ + public void testSubmitCallable() throws Exception { + ExecutorService e = new DirectExecutorService(); + Future future = e.submit(new StringTask()); + String result = future.get(); + assertSame(TEST_STRING, result); + } + + /** + * Completed submit(runnable) returns successfully + */ + public void testSubmitRunnable() throws Exception { + ExecutorService e = new DirectExecutorService(); + Future future = e.submit(new NoOpRunnable()); + future.get(); + assertTrue(future.isDone()); + } + + /** + * Completed submit(runnable, result) returns result + */ + public void testSubmitRunnable2() throws Exception { + ExecutorService e = new DirectExecutorService(); + Future future = e.submit(new NoOpRunnable(), TEST_STRING); + String result = future.get(); + assertSame(TEST_STRING, result); + } + + /** + * A submitted privileged action runs to completion + */ + public void testSubmitPrivilegedAction() throws Exception { + Runnable r = new CheckedRunnable() { + public void realRun() throws Exception { + ExecutorService e = new DirectExecutorService(); + Future future = e.submit(Executors.callable(new PrivilegedAction() { + public Object run() { + return TEST_STRING; + }})); + + assertSame(TEST_STRING, future.get()); + }}; + + runWithPermissions(r, + new RuntimePermission("getClassLoader"), + new RuntimePermission("setContextClassLoader"), + new RuntimePermission("modifyThread")); + } + + /** + * A submitted privileged exception action runs to completion + */ + public void testSubmitPrivilegedExceptionAction() throws Exception { + Runnable r = new CheckedRunnable() { + public void realRun() throws Exception { + ExecutorService e = new DirectExecutorService(); + Future future = e.submit(Executors.callable(new PrivilegedExceptionAction() { + public Object run() { + return TEST_STRING; + }})); + + assertSame(TEST_STRING, future.get()); + }}; + + runWithPermissions(r); + } + + /** + * A submitted failed privileged exception action reports exception + */ + public void testSubmitFailedPrivilegedExceptionAction() throws Exception { + Runnable r = new CheckedRunnable() { + public void realRun() throws Exception { + ExecutorService e = new DirectExecutorService(); + Future future = e.submit(Executors.callable(new PrivilegedExceptionAction() { + public Object run() throws Exception { + throw new IndexOutOfBoundsException(); + }})); + + try { + future.get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof IndexOutOfBoundsException); + }}}; + + runWithPermissions(r); + } + + /** + * execute(null runnable) throws NPE + */ + public void testExecuteNullRunnable() { + ExecutorService e = new DirectExecutorService(); + try { + e.submit((Runnable) null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * submit(null callable) throws NPE + */ + public void testSubmitNullCallable() { + ExecutorService e = new DirectExecutorService(); + try { + e.submit((Callable) null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * submit(callable).get() throws InterruptedException if interrupted + */ + public void testInterruptedSubmit() throws InterruptedException { + final CountDownLatch submitted = new CountDownLatch(1); + final CountDownLatch quittingTime = new CountDownLatch(1); + final Callable awaiter = new CheckedCallable() { + public Void realCall() throws InterruptedException { + assertTrue(quittingTime.await(2*LONG_DELAY_MS, MILLISECONDS)); + return null; + }}; + final ExecutorService p + = new ThreadPoolExecutor(1,1,60, TimeUnit.SECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p, quittingTime)) { + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws Exception { + Future future = p.submit(awaiter); + submitted.countDown(); + future.get(); + }}); + + await(submitted); + t.interrupt(); + awaitTermination(t); + } + } + + /** + * get of submit(callable) throws ExecutionException if callable + * throws exception + */ + public void testSubmitEE() throws InterruptedException { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 1, + 60, TimeUnit.SECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + Callable c = new Callable() { + public Object call() { throw new ArithmeticException(); }}; + try { + p.submit(c).get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof ArithmeticException); + } + } + } + + /** + * invokeAny(null) throws NPE + */ + public void testInvokeAny1() throws Exception { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * invokeAny(empty collection) throws IAE + */ + public void testInvokeAny2() throws Exception { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(new ArrayList>()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * invokeAny(c) throws NPE if c has null elements + */ + public void testInvokeAny3() throws Exception { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new Callable() { + public Long call() { throw new ArithmeticException(); }}); + l.add(null); + try { + e.invokeAny(l); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * invokeAny(c) throws ExecutionException if no task in c completes + */ + public void testInvokeAny4() throws InterruptedException { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + try { + e.invokeAny(l); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + } + } + + /** + * invokeAny(c) returns result of some task in c if at least one completes + */ + public void testInvokeAny5() throws Exception { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(new StringTask()); + String result = e.invokeAny(l); + assertSame(TEST_STRING, result); + } + } + + /** + * invokeAll(null) throws NPE + */ + public void testInvokeAll1() throws InterruptedException { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAll(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * invokeAll(empty collection) returns empty collection + */ + public void testInvokeAll2() throws InterruptedException { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + List> r = e.invokeAll(new ArrayList>()); + assertTrue(r.isEmpty()); + } + } + + /** + * invokeAll(c) throws NPE if c has null elements + */ + public void testInvokeAll3() throws InterruptedException { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(null); + try { + e.invokeAll(l); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * get of returned element of invokeAll(c) throws exception on failed task + */ + public void testInvokeAll4() throws Exception { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + List> futures = e.invokeAll(l); + assertEquals(1, futures.size()); + try { + futures.get(0).get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + } + } + + /** + * invokeAll(c) returns results of all completed tasks in c + */ + public void testInvokeAll5() throws Exception { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(new StringTask()); + List> futures = e.invokeAll(l); + assertEquals(2, futures.size()); + for (Future future : futures) + assertSame(TEST_STRING, future.get()); + } + } + + /** + * timed invokeAny(null) throws NPE + */ + public void testTimedInvokeAny1() throws Exception { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(null, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed invokeAny(null time unit) throws NPE + */ + public void testTimedInvokeAnyNullTimeUnit() throws Exception { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + try { + e.invokeAny(l, MEDIUM_DELAY_MS, null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed invokeAny(empty collection) throws IAE + */ + public void testTimedInvokeAny2() throws Exception { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(new ArrayList>(), + MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * timed invokeAny(c) throws NPE if c has null elements + */ + public void testTimedInvokeAny3() throws Exception { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new Callable() { + public Long call() { throw new ArithmeticException(); }}); + l.add(null); + try { + e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed invokeAny(c) throws ExecutionException if no task completes + */ + public void testTimedInvokeAny4() throws Exception { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + long startTime = System.nanoTime(); + List> l = new ArrayList>(); + l.add(new NPETask()); + try { + e.invokeAny(l, LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + } + + /** + * timed invokeAny(c) returns result of some task in c + */ + public void testTimedInvokeAny5() throws Exception { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + long startTime = System.nanoTime(); + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(new StringTask()); + String result = e.invokeAny(l, LONG_DELAY_MS, MILLISECONDS); + assertSame(TEST_STRING, result); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + } + + /** + * timed invokeAll(null) throws NPE + */ + public void testTimedInvokeAll1() throws InterruptedException { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAll(null, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed invokeAll(null time unit) throws NPE + */ + public void testTimedInvokeAllNullTimeUnit() throws InterruptedException { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + try { + e.invokeAll(l, MEDIUM_DELAY_MS, null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed invokeAll(empty collection) returns empty collection + */ + public void testTimedInvokeAll2() throws InterruptedException { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + List> r = e.invokeAll(new ArrayList>(), MEDIUM_DELAY_MS, MILLISECONDS); + assertTrue(r.isEmpty()); + } + } + + /** + * timed invokeAll(c) throws NPE if c has null elements + */ + public void testTimedInvokeAll3() throws InterruptedException { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(null); + try { + e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * get of returned element of invokeAll(c) throws exception on failed task + */ + public void testTimedInvokeAll4() throws Exception { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + List> futures = + e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS); + assertEquals(1, futures.size()); + try { + futures.get(0).get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + } + } + + /** + * timed invokeAll(c) returns results of all completed tasks in c + */ + public void testTimedInvokeAll5() throws Exception { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(new StringTask()); + List> futures = + e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS); + assertEquals(2, futures.size()); + for (Future future : futures) + assertSame(TEST_STRING, future.get()); + } + } + + /** + * timed invokeAll cancels tasks not completed by timeout + */ + public void testTimedInvokeAll6() throws Exception { + final ExecutorService e = new DirectExecutorService(); + try (PoolCleaner cleaner = cleaner(e)) { + for (long timeout = timeoutMillis();;) { + List> tasks = new ArrayList<>(); + tasks.add(new StringTask("0")); + tasks.add(Executors.callable(possiblyInterruptedRunnable(timeout), + TEST_STRING)); + tasks.add(new StringTask("2")); + long startTime = System.nanoTime(); + List> futures = + e.invokeAll(tasks, timeout, MILLISECONDS); + assertEquals(tasks.size(), futures.size()); + assertTrue(millisElapsedSince(startTime) >= timeout); + for (Future future : futures) + assertTrue(future.isDone()); + try { + assertEquals("0", futures.get(0).get()); + assertEquals(TEST_STRING, futures.get(1).get()); + } catch (CancellationException retryWithLongerTimeout) { + // unusual delay before starting second task + timeout *= 2; + if (timeout >= LONG_DELAY_MS / 2) + fail("expected exactly one task to be cancelled"); + continue; + } + assertTrue(futures.get(2).isCancelled()); + break; + } + } + } + +} diff --git a/jdk/test/java/util/concurrent/tck/AbstractQueueTest.java b/jdk/test/java/util/concurrent/tck/AbstractQueueTest.java new file mode 100644 index 00000000000..ddc769d3a45 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/AbstractQueueTest.java @@ -0,0 +1,205 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import java.util.AbstractQueue; +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AbstractQueueTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(AbstractQueueTest.class); + } + + static class Succeed extends AbstractQueue { + public boolean offer(Integer x) { + if (x == null) throw new NullPointerException(); + return true; + } + public Integer peek() { return one; } + public Integer poll() { return one; } + public int size() { return 0; } + public Iterator iterator() { return null; } // not needed + } + + static class Fail extends AbstractQueue { + public boolean offer(Integer x) { + if (x == null) throw new NullPointerException(); + return false; + } + public Integer peek() { return null; } + public Integer poll() { return null; } + public int size() { return 0; } + public Iterator iterator() { return null; } // not needed + } + + /** + * add returns true if offer succeeds + */ + public void testAddS() { + Succeed q = new Succeed(); + assertTrue(q.add(two)); + } + + /** + * add throws ISE true if offer fails + */ + public void testAddF() { + Fail q = new Fail(); + try { + q.add(one); + shouldThrow(); + } catch (IllegalStateException success) {} + } + + /** + * add throws NPE if offer does + */ + public void testAddNPE() { + Succeed q = new Succeed(); + try { + q.add(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * remove returns normally if poll succeeds + */ + public void testRemoveS() { + Succeed q = new Succeed(); + q.remove(); + } + + /** + * remove throws NSEE if poll returns null + */ + public void testRemoveF() { + Fail q = new Fail(); + try { + q.remove(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * element returns normally if peek succeeds + */ + public void testElementS() { + Succeed q = new Succeed(); + q.element(); + } + + /** + * element throws NSEE if peek returns null + */ + public void testElementF() { + Fail q = new Fail(); + try { + q.element(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * addAll(null) throws NPE + */ + public void testAddAll1() { + Succeed q = new Succeed(); + try { + q.addAll(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll(this) throws IAE + */ + public void testAddAllSelf() { + Succeed q = new Succeed(); + try { + q.addAll(q); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * addAll of a collection with null elements throws NPE + */ + public void testAddAll2() { + Succeed q = new Succeed(); + Integer[] ints = new Integer[SIZE]; + try { + q.addAll(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll of a collection with any null elements throws NPE after + * possibly adding some elements + */ + public void testAddAll3() { + Succeed q = new Succeed(); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); + try { + q.addAll(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll throws ISE if an add fails + */ + public void testAddAll4() { + Fail q = new Fail(); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(i); + try { + q.addAll(Arrays.asList(ints)); + shouldThrow(); + } catch (IllegalStateException success) {} + } + +} diff --git a/jdk/test/java/util/concurrent/tck/AbstractQueuedLongSynchronizerTest.java b/jdk/test/java/util/concurrent/tck/AbstractQueuedLongSynchronizerTest.java new file mode 100644 index 00000000000..88bdd13b4dc --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/AbstractQueuedLongSynchronizerTest.java @@ -0,0 +1,1280 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.concurrent.locks.AbstractQueuedLongSynchronizer; +import java.util.concurrent.locks.AbstractQueuedLongSynchronizer.ConditionObject; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AbstractQueuedLongSynchronizerTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(AbstractQueuedLongSynchronizerTest.class); + } + + /** + * A simple mutex class, adapted from the class javadoc. Exclusive + * acquire tests exercise this as a sample user extension. + */ + static class Mutex extends AbstractQueuedLongSynchronizer { + /** An eccentric value > 32 bits for locked synchronizer state. */ + static final long LOCKED = (1L << 63) | (1L << 15); + + static final long UNLOCKED = 0; + + public boolean isHeldExclusively() { + long state = getState(); + assertTrue(state == UNLOCKED || state == LOCKED); + return state == LOCKED; + } + + public boolean tryAcquire(long acquires) { + assertEquals(LOCKED, acquires); + return compareAndSetState(UNLOCKED, LOCKED); + } + + public boolean tryRelease(long releases) { + if (getState() != LOCKED) throw new IllegalMonitorStateException(); + setState(UNLOCKED); + return true; + } + + public boolean tryAcquireNanos(long nanos) throws InterruptedException { + return tryAcquireNanos(LOCKED, nanos); + } + + public boolean tryAcquire() { + return tryAcquire(LOCKED); + } + + public boolean tryRelease() { + return tryRelease(LOCKED); + } + + public void acquire() { + acquire(LOCKED); + } + + public void acquireInterruptibly() throws InterruptedException { + acquireInterruptibly(LOCKED); + } + + public void release() { + release(LOCKED); + } + + public ConditionObject newCondition() { + return new ConditionObject(); + } + } + + /** + * A simple latch class, to test shared mode. + */ + static class BooleanLatch extends AbstractQueuedLongSynchronizer { + public boolean isSignalled() { return getState() != 0; } + + public long tryAcquireShared(long ignore) { + return isSignalled() ? 1 : -1; + } + + public boolean tryReleaseShared(long ignore) { + setState(1L << 62); + return true; + } + } + + /** + * A runnable calling acquireInterruptibly that does not expect to + * be interrupted. + */ + class InterruptibleSyncRunnable extends CheckedRunnable { + final Mutex sync; + InterruptibleSyncRunnable(Mutex sync) { this.sync = sync; } + public void realRun() throws InterruptedException { + sync.acquireInterruptibly(); + } + } + + /** + * A runnable calling acquireInterruptibly that expects to be + * interrupted. + */ + class InterruptedSyncRunnable extends CheckedInterruptedRunnable { + final Mutex sync; + InterruptedSyncRunnable(Mutex sync) { this.sync = sync; } + public void realRun() throws InterruptedException { + sync.acquireInterruptibly(); + } + } + + /** A constant to clarify calls to checking methods below. */ + static final Thread[] NO_THREADS = new Thread[0]; + + /** + * Spin-waits until sync.isQueued(t) becomes true. + */ + void waitForQueuedThread(AbstractQueuedLongSynchronizer sync, + Thread t) { + long startTime = System.nanoTime(); + while (!sync.isQueued(t)) { + if (millisElapsedSince(startTime) > LONG_DELAY_MS) + throw new AssertionFailedError("timed out"); + Thread.yield(); + } + assertTrue(t.isAlive()); + } + + /** + * Checks that sync has exactly the given queued threads. + */ + void assertHasQueuedThreads(AbstractQueuedLongSynchronizer sync, + Thread... expected) { + Collection actual = sync.getQueuedThreads(); + assertEquals(expected.length > 0, sync.hasQueuedThreads()); + assertEquals(expected.length, sync.getQueueLength()); + assertEquals(expected.length, actual.size()); + assertEquals(expected.length == 0, actual.isEmpty()); + assertEquals(new HashSet(actual), + new HashSet(Arrays.asList(expected))); + } + + /** + * Checks that sync has exactly the given (exclusive) queued threads. + */ + void assertHasExclusiveQueuedThreads(AbstractQueuedLongSynchronizer sync, + Thread... expected) { + assertHasQueuedThreads(sync, expected); + assertEquals(new HashSet(sync.getExclusiveQueuedThreads()), + new HashSet(sync.getQueuedThreads())); + assertEquals(0, sync.getSharedQueuedThreads().size()); + assertTrue(sync.getSharedQueuedThreads().isEmpty()); + } + + /** + * Checks that sync has exactly the given (shared) queued threads. + */ + void assertHasSharedQueuedThreads(AbstractQueuedLongSynchronizer sync, + Thread... expected) { + assertHasQueuedThreads(sync, expected); + assertEquals(new HashSet(sync.getSharedQueuedThreads()), + new HashSet(sync.getQueuedThreads())); + assertEquals(0, sync.getExclusiveQueuedThreads().size()); + assertTrue(sync.getExclusiveQueuedThreads().isEmpty()); + } + + /** + * Checks that condition c has exactly the given waiter threads, + * after acquiring mutex. + */ + void assertHasWaitersUnlocked(Mutex sync, ConditionObject c, + Thread... threads) { + sync.acquire(); + assertHasWaitersLocked(sync, c, threads); + sync.release(); + } + + /** + * Checks that condition c has exactly the given waiter threads. + */ + void assertHasWaitersLocked(Mutex sync, ConditionObject c, + Thread... threads) { + assertEquals(threads.length > 0, sync.hasWaiters(c)); + assertEquals(threads.length, sync.getWaitQueueLength(c)); + assertEquals(threads.length == 0, sync.getWaitingThreads(c).isEmpty()); + assertEquals(threads.length, sync.getWaitingThreads(c).size()); + assertEquals(new HashSet(sync.getWaitingThreads(c)), + new HashSet(Arrays.asList(threads))); + } + + enum AwaitMethod { await, awaitTimed, awaitNanos, awaitUntil } + + /** + * Awaits condition using the specified AwaitMethod. + */ + void await(ConditionObject c, AwaitMethod awaitMethod) + throws InterruptedException { + long timeoutMillis = 2 * LONG_DELAY_MS; + switch (awaitMethod) { + case await: + c.await(); + break; + case awaitTimed: + assertTrue(c.await(timeoutMillis, MILLISECONDS)); + break; + case awaitNanos: + long nanosTimeout = MILLISECONDS.toNanos(timeoutMillis); + long nanosRemaining = c.awaitNanos(nanosTimeout); + assertTrue(nanosRemaining > 0); + break; + case awaitUntil: + assertTrue(c.awaitUntil(delayedDate(timeoutMillis))); + break; + default: + throw new AssertionError(); + } + } + + /** + * Checks that awaiting the given condition times out (using the + * default timeout duration). + */ + void assertAwaitTimesOut(ConditionObject c, AwaitMethod awaitMethod) { + long timeoutMillis = timeoutMillis(); + long startTime; + try { + switch (awaitMethod) { + case awaitTimed: + startTime = System.nanoTime(); + assertFalse(c.await(timeoutMillis, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + break; + case awaitNanos: + startTime = System.nanoTime(); + long nanosTimeout = MILLISECONDS.toNanos(timeoutMillis); + long nanosRemaining = c.awaitNanos(nanosTimeout); + assertTrue(nanosRemaining <= 0); + assertTrue(nanosRemaining > -MILLISECONDS.toNanos(LONG_DELAY_MS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + break; + case awaitUntil: + // We shouldn't assume that nanoTime and currentTimeMillis + // use the same time source, so don't use nanoTime here. + java.util.Date delayedDate = delayedDate(timeoutMillis()); + assertFalse(c.awaitUntil(delayedDate(timeoutMillis))); + assertTrue(new java.util.Date().getTime() >= delayedDate.getTime()); + break; + default: + throw new UnsupportedOperationException(); + } + } catch (InterruptedException ie) { threadUnexpectedException(ie); } + } + + /** + * isHeldExclusively is false upon construction + */ + public void testIsHeldExclusively() { + Mutex sync = new Mutex(); + assertFalse(sync.isHeldExclusively()); + } + + /** + * acquiring released sync succeeds + */ + public void testAcquire() { + Mutex sync = new Mutex(); + sync.acquire(); + assertTrue(sync.isHeldExclusively()); + sync.release(); + assertFalse(sync.isHeldExclusively()); + } + + /** + * tryAcquire on a released sync succeeds + */ + public void testTryAcquire() { + Mutex sync = new Mutex(); + assertTrue(sync.tryAcquire()); + assertTrue(sync.isHeldExclusively()); + sync.release(); + assertFalse(sync.isHeldExclusively()); + } + + /** + * hasQueuedThreads reports whether there are waiting threads + */ + public void testHasQueuedThreads() { + final Mutex sync = new Mutex(); + assertFalse(sync.hasQueuedThreads()); + sync.acquire(); + Thread t1 = newStartedThread(new InterruptedSyncRunnable(sync)); + waitForQueuedThread(sync, t1); + assertTrue(sync.hasQueuedThreads()); + Thread t2 = newStartedThread(new InterruptibleSyncRunnable(sync)); + waitForQueuedThread(sync, t2); + assertTrue(sync.hasQueuedThreads()); + t1.interrupt(); + awaitTermination(t1); + assertTrue(sync.hasQueuedThreads()); + sync.release(); + awaitTermination(t2); + assertFalse(sync.hasQueuedThreads()); + } + + /** + * isQueued(null) throws NullPointerException + */ + public void testIsQueuedNPE() { + final Mutex sync = new Mutex(); + try { + sync.isQueued(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * isQueued reports whether a thread is queued + */ + public void testIsQueued() { + final Mutex sync = new Mutex(); + Thread t1 = new Thread(new InterruptedSyncRunnable(sync)); + Thread t2 = new Thread(new InterruptibleSyncRunnable(sync)); + assertFalse(sync.isQueued(t1)); + assertFalse(sync.isQueued(t2)); + sync.acquire(); + t1.start(); + waitForQueuedThread(sync, t1); + assertTrue(sync.isQueued(t1)); + assertFalse(sync.isQueued(t2)); + t2.start(); + waitForQueuedThread(sync, t2); + assertTrue(sync.isQueued(t1)); + assertTrue(sync.isQueued(t2)); + t1.interrupt(); + awaitTermination(t1); + assertFalse(sync.isQueued(t1)); + assertTrue(sync.isQueued(t2)); + sync.release(); + awaitTermination(t2); + assertFalse(sync.isQueued(t1)); + assertFalse(sync.isQueued(t2)); + } + + /** + * getFirstQueuedThread returns first waiting thread or null if none + */ + public void testGetFirstQueuedThread() { + final Mutex sync = new Mutex(); + assertNull(sync.getFirstQueuedThread()); + sync.acquire(); + Thread t1 = newStartedThread(new InterruptedSyncRunnable(sync)); + waitForQueuedThread(sync, t1); + assertEquals(t1, sync.getFirstQueuedThread()); + Thread t2 = newStartedThread(new InterruptibleSyncRunnable(sync)); + waitForQueuedThread(sync, t2); + assertEquals(t1, sync.getFirstQueuedThread()); + t1.interrupt(); + awaitTermination(t1); + assertEquals(t2, sync.getFirstQueuedThread()); + sync.release(); + awaitTermination(t2); + assertNull(sync.getFirstQueuedThread()); + } + + /** + * hasContended reports false if no thread has ever blocked, else true + */ + public void testHasContended() { + final Mutex sync = new Mutex(); + assertFalse(sync.hasContended()); + sync.acquire(); + assertFalse(sync.hasContended()); + Thread t1 = newStartedThread(new InterruptedSyncRunnable(sync)); + waitForQueuedThread(sync, t1); + assertTrue(sync.hasContended()); + Thread t2 = newStartedThread(new InterruptibleSyncRunnable(sync)); + waitForQueuedThread(sync, t2); + assertTrue(sync.hasContended()); + t1.interrupt(); + awaitTermination(t1); + assertTrue(sync.hasContended()); + sync.release(); + awaitTermination(t2); + assertTrue(sync.hasContended()); + } + + /** + * getQueuedThreads returns all waiting threads + */ + public void testGetQueuedThreads() { + final Mutex sync = new Mutex(); + Thread t1 = new Thread(new InterruptedSyncRunnable(sync)); + Thread t2 = new Thread(new InterruptibleSyncRunnable(sync)); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + sync.acquire(); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + t1.start(); + waitForQueuedThread(sync, t1); + assertHasExclusiveQueuedThreads(sync, t1); + assertTrue(sync.getQueuedThreads().contains(t1)); + assertFalse(sync.getQueuedThreads().contains(t2)); + t2.start(); + waitForQueuedThread(sync, t2); + assertHasExclusiveQueuedThreads(sync, t1, t2); + assertTrue(sync.getQueuedThreads().contains(t1)); + assertTrue(sync.getQueuedThreads().contains(t2)); + t1.interrupt(); + awaitTermination(t1); + assertHasExclusiveQueuedThreads(sync, t2); + sync.release(); + awaitTermination(t2); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + } + + /** + * getExclusiveQueuedThreads returns all exclusive waiting threads + */ + public void testGetExclusiveQueuedThreads() { + final Mutex sync = new Mutex(); + Thread t1 = new Thread(new InterruptedSyncRunnable(sync)); + Thread t2 = new Thread(new InterruptibleSyncRunnable(sync)); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + sync.acquire(); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + t1.start(); + waitForQueuedThread(sync, t1); + assertHasExclusiveQueuedThreads(sync, t1); + assertTrue(sync.getExclusiveQueuedThreads().contains(t1)); + assertFalse(sync.getExclusiveQueuedThreads().contains(t2)); + t2.start(); + waitForQueuedThread(sync, t2); + assertHasExclusiveQueuedThreads(sync, t1, t2); + assertTrue(sync.getExclusiveQueuedThreads().contains(t1)); + assertTrue(sync.getExclusiveQueuedThreads().contains(t2)); + t1.interrupt(); + awaitTermination(t1); + assertHasExclusiveQueuedThreads(sync, t2); + sync.release(); + awaitTermination(t2); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + } + + /** + * getSharedQueuedThreads does not include exclusively waiting threads + */ + public void testGetSharedQueuedThreads_Exclusive() { + final Mutex sync = new Mutex(); + assertTrue(sync.getSharedQueuedThreads().isEmpty()); + sync.acquire(); + assertTrue(sync.getSharedQueuedThreads().isEmpty()); + Thread t1 = newStartedThread(new InterruptedSyncRunnable(sync)); + waitForQueuedThread(sync, t1); + assertTrue(sync.getSharedQueuedThreads().isEmpty()); + Thread t2 = newStartedThread(new InterruptibleSyncRunnable(sync)); + waitForQueuedThread(sync, t2); + assertTrue(sync.getSharedQueuedThreads().isEmpty()); + t1.interrupt(); + awaitTermination(t1); + assertTrue(sync.getSharedQueuedThreads().isEmpty()); + sync.release(); + awaitTermination(t2); + assertTrue(sync.getSharedQueuedThreads().isEmpty()); + } + + /** + * getSharedQueuedThreads returns all shared waiting threads + */ + public void testGetSharedQueuedThreads_Shared() { + final BooleanLatch l = new BooleanLatch(); + assertHasSharedQueuedThreads(l, NO_THREADS); + Thread t1 = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + l.acquireSharedInterruptibly(0); + }}); + waitForQueuedThread(l, t1); + assertHasSharedQueuedThreads(l, t1); + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + l.acquireSharedInterruptibly(0); + }}); + waitForQueuedThread(l, t2); + assertHasSharedQueuedThreads(l, t1, t2); + t1.interrupt(); + awaitTermination(t1); + assertHasSharedQueuedThreads(l, t2); + assertTrue(l.releaseShared(0)); + awaitTermination(t2); + assertHasSharedQueuedThreads(l, NO_THREADS); + } + + /** + * tryAcquireNanos is interruptible + */ + public void testTryAcquireNanos_Interruptible() { + final Mutex sync = new Mutex(); + sync.acquire(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + sync.tryAcquireNanos(MILLISECONDS.toNanos(2 * LONG_DELAY_MS)); + }}); + + waitForQueuedThread(sync, t); + t.interrupt(); + awaitTermination(t); + } + + /** + * tryAcquire on exclusively held sync fails + */ + public void testTryAcquireWhenSynced() { + final Mutex sync = new Mutex(); + sync.acquire(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + assertFalse(sync.tryAcquire()); + }}); + + awaitTermination(t); + sync.release(); + } + + /** + * tryAcquireNanos on an exclusively held sync times out + */ + public void testAcquireNanos_Timeout() { + final Mutex sync = new Mutex(); + sync.acquire(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + long nanos = MILLISECONDS.toNanos(timeoutMillis()); + assertFalse(sync.tryAcquireNanos(nanos)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + }}); + + awaitTermination(t); + sync.release(); + } + + /** + * getState is true when acquired and false when not + */ + public void testGetState() { + final Mutex sync = new Mutex(); + sync.acquire(); + assertTrue(sync.isHeldExclusively()); + sync.release(); + assertFalse(sync.isHeldExclusively()); + + final BooleanLatch acquired = new BooleanLatch(); + final BooleanLatch done = new BooleanLatch(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + sync.acquire(); + assertTrue(acquired.releaseShared(0)); + done.acquireShared(0); + sync.release(); + }}); + + acquired.acquireShared(0); + assertTrue(sync.isHeldExclusively()); + assertTrue(done.releaseShared(0)); + awaitTermination(t); + assertFalse(sync.isHeldExclusively()); + } + + /** + * acquireInterruptibly succeeds when released, else is interruptible + */ + public void testAcquireInterruptibly() throws InterruptedException { + final Mutex sync = new Mutex(); + final BooleanLatch threadStarted = new BooleanLatch(); + sync.acquireInterruptibly(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + assertTrue(threadStarted.releaseShared(0)); + sync.acquireInterruptibly(); + }}); + + threadStarted.acquireShared(0); + waitForQueuedThread(sync, t); + t.interrupt(); + awaitTermination(t); + assertTrue(sync.isHeldExclusively()); + } + + /** + * owns is true for a condition created by sync else false + */ + public void testOwns() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + final Mutex sync2 = new Mutex(); + assertTrue(sync.owns(c)); + assertFalse(sync2.owns(c)); + } + + /** + * Calling await without holding sync throws IllegalMonitorStateException + */ + public void testAwait_IMSE() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + for (AwaitMethod awaitMethod : AwaitMethod.values()) { + long startTime = System.nanoTime(); + try { + await(c, awaitMethod); + shouldThrow(); + } catch (IllegalMonitorStateException success) { + } catch (InterruptedException e) { threadUnexpectedException(e); } + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + } + + /** + * Calling signal without holding sync throws IllegalMonitorStateException + */ + public void testSignal_IMSE() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + try { + c.signal(); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + assertHasWaitersUnlocked(sync, c, NO_THREADS); + } + + /** + * Calling signalAll without holding sync throws IllegalMonitorStateException + */ + public void testSignalAll_IMSE() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + try { + c.signalAll(); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * await/awaitNanos/awaitUntil without a signal times out + */ + public void testAwaitTimed_Timeout() { testAwait_Timeout(AwaitMethod.awaitTimed); } + public void testAwaitNanos_Timeout() { testAwait_Timeout(AwaitMethod.awaitNanos); } + public void testAwaitUntil_Timeout() { testAwait_Timeout(AwaitMethod.awaitUntil); } + public void testAwait_Timeout(AwaitMethod awaitMethod) { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + sync.acquire(); + assertAwaitTimesOut(c, awaitMethod); + sync.release(); + } + + /** + * await/awaitNanos/awaitUntil returns when signalled + */ + public void testSignal_await() { testSignal(AwaitMethod.await); } + public void testSignal_awaitTimed() { testSignal(AwaitMethod.awaitTimed); } + public void testSignal_awaitNanos() { testSignal(AwaitMethod.awaitNanos); } + public void testSignal_awaitUntil() { testSignal(AwaitMethod.awaitUntil); } + public void testSignal(final AwaitMethod awaitMethod) { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + final BooleanLatch acquired = new BooleanLatch(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + sync.acquire(); + assertTrue(acquired.releaseShared(0)); + await(c, awaitMethod); + sync.release(); + }}); + + acquired.acquireShared(0); + sync.acquire(); + assertHasWaitersLocked(sync, c, t); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + c.signal(); + assertHasWaitersLocked(sync, c, NO_THREADS); + assertHasExclusiveQueuedThreads(sync, t); + sync.release(); + awaitTermination(t); + } + + /** + * hasWaiters(null) throws NullPointerException + */ + public void testHasWaitersNPE() { + final Mutex sync = new Mutex(); + try { + sync.hasWaiters(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * getWaitQueueLength(null) throws NullPointerException + */ + public void testGetWaitQueueLengthNPE() { + final Mutex sync = new Mutex(); + try { + sync.getWaitQueueLength(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * getWaitingThreads throws NPE if null + */ + public void testGetWaitingThreadsNPE() { + final Mutex sync = new Mutex(); + try { + sync.getWaitingThreads(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * hasWaiters throws IllegalArgumentException if not owned + */ + public void testHasWaitersIAE() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + final Mutex sync2 = new Mutex(); + try { + sync2.hasWaiters(c); + shouldThrow(); + } catch (IllegalArgumentException success) {} + assertHasWaitersUnlocked(sync, c, NO_THREADS); + } + + /** + * hasWaiters throws IllegalMonitorStateException if not synced + */ + public void testHasWaitersIMSE() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + try { + sync.hasWaiters(c); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + assertHasWaitersUnlocked(sync, c, NO_THREADS); + } + + /** + * getWaitQueueLength throws IllegalArgumentException if not owned + */ + public void testGetWaitQueueLengthIAE() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + final Mutex sync2 = new Mutex(); + try { + sync2.getWaitQueueLength(c); + shouldThrow(); + } catch (IllegalArgumentException success) {} + assertHasWaitersUnlocked(sync, c, NO_THREADS); + } + + /** + * getWaitQueueLength throws IllegalMonitorStateException if not synced + */ + public void testGetWaitQueueLengthIMSE() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + try { + sync.getWaitQueueLength(c); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + assertHasWaitersUnlocked(sync, c, NO_THREADS); + } + + /** + * getWaitingThreads throws IllegalArgumentException if not owned + */ + public void testGetWaitingThreadsIAE() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + final Mutex sync2 = new Mutex(); + try { + sync2.getWaitingThreads(c); + shouldThrow(); + } catch (IllegalArgumentException success) {} + assertHasWaitersUnlocked(sync, c, NO_THREADS); + } + + /** + * getWaitingThreads throws IllegalMonitorStateException if not synced + */ + public void testGetWaitingThreadsIMSE() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + try { + sync.getWaitingThreads(c); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + assertHasWaitersUnlocked(sync, c, NO_THREADS); + } + + /** + * hasWaiters returns true when a thread is waiting, else false + */ + public void testHasWaiters() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + final BooleanLatch acquired = new BooleanLatch(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + sync.acquire(); + assertHasWaitersLocked(sync, c, NO_THREADS); + assertFalse(sync.hasWaiters(c)); + assertTrue(acquired.releaseShared(0)); + c.await(); + sync.release(); + }}); + + acquired.acquireShared(0); + sync.acquire(); + assertHasWaitersLocked(sync, c, t); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + assertTrue(sync.hasWaiters(c)); + c.signal(); + assertHasWaitersLocked(sync, c, NO_THREADS); + assertHasExclusiveQueuedThreads(sync, t); + assertFalse(sync.hasWaiters(c)); + sync.release(); + + awaitTermination(t); + assertHasWaitersUnlocked(sync, c, NO_THREADS); + } + + /** + * getWaitQueueLength returns number of waiting threads + */ + public void testGetWaitQueueLength() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + final BooleanLatch acquired1 = new BooleanLatch(); + final BooleanLatch acquired2 = new BooleanLatch(); + final Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + sync.acquire(); + assertHasWaitersLocked(sync, c, NO_THREADS); + assertEquals(0, sync.getWaitQueueLength(c)); + assertTrue(acquired1.releaseShared(0)); + c.await(); + sync.release(); + }}); + acquired1.acquireShared(0); + sync.acquire(); + assertHasWaitersLocked(sync, c, t1); + assertEquals(1, sync.getWaitQueueLength(c)); + sync.release(); + + final Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + sync.acquire(); + assertHasWaitersLocked(sync, c, t1); + assertEquals(1, sync.getWaitQueueLength(c)); + assertTrue(acquired2.releaseShared(0)); + c.await(); + sync.release(); + }}); + acquired2.acquireShared(0); + sync.acquire(); + assertHasWaitersLocked(sync, c, t1, t2); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + assertEquals(2, sync.getWaitQueueLength(c)); + c.signalAll(); + assertHasWaitersLocked(sync, c, NO_THREADS); + assertHasExclusiveQueuedThreads(sync, t1, t2); + assertEquals(0, sync.getWaitQueueLength(c)); + sync.release(); + + awaitTermination(t1); + awaitTermination(t2); + assertHasWaitersUnlocked(sync, c, NO_THREADS); + } + + /** + * getWaitingThreads returns only and all waiting threads + */ + public void testGetWaitingThreads() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + final BooleanLatch acquired1 = new BooleanLatch(); + final BooleanLatch acquired2 = new BooleanLatch(); + final Thread t1 = new Thread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + sync.acquire(); + assertHasWaitersLocked(sync, c, NO_THREADS); + assertTrue(sync.getWaitingThreads(c).isEmpty()); + assertTrue(acquired1.releaseShared(0)); + c.await(); + sync.release(); + }}); + + final Thread t2 = new Thread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + sync.acquire(); + assertHasWaitersLocked(sync, c, t1); + assertTrue(sync.getWaitingThreads(c).contains(t1)); + assertFalse(sync.getWaitingThreads(c).isEmpty()); + assertEquals(1, sync.getWaitingThreads(c).size()); + assertTrue(acquired2.releaseShared(0)); + c.await(); + sync.release(); + }}); + + sync.acquire(); + assertHasWaitersLocked(sync, c, NO_THREADS); + assertFalse(sync.getWaitingThreads(c).contains(t1)); + assertFalse(sync.getWaitingThreads(c).contains(t2)); + assertTrue(sync.getWaitingThreads(c).isEmpty()); + assertEquals(0, sync.getWaitingThreads(c).size()); + sync.release(); + + t1.start(); + acquired1.acquireShared(0); + sync.acquire(); + assertHasWaitersLocked(sync, c, t1); + assertTrue(sync.getWaitingThreads(c).contains(t1)); + assertFalse(sync.getWaitingThreads(c).contains(t2)); + assertFalse(sync.getWaitingThreads(c).isEmpty()); + assertEquals(1, sync.getWaitingThreads(c).size()); + sync.release(); + + t2.start(); + acquired2.acquireShared(0); + sync.acquire(); + assertHasWaitersLocked(sync, c, t1, t2); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + assertTrue(sync.getWaitingThreads(c).contains(t1)); + assertTrue(sync.getWaitingThreads(c).contains(t2)); + assertFalse(sync.getWaitingThreads(c).isEmpty()); + assertEquals(2, sync.getWaitingThreads(c).size()); + c.signalAll(); + assertHasWaitersLocked(sync, c, NO_THREADS); + assertHasExclusiveQueuedThreads(sync, t1, t2); + assertFalse(sync.getWaitingThreads(c).contains(t1)); + assertFalse(sync.getWaitingThreads(c).contains(t2)); + assertTrue(sync.getWaitingThreads(c).isEmpty()); + assertEquals(0, sync.getWaitingThreads(c).size()); + sync.release(); + + awaitTermination(t1); + awaitTermination(t2); + assertHasWaitersUnlocked(sync, c, NO_THREADS); + } + + /** + * awaitUninterruptibly is uninterruptible + */ + public void testAwaitUninterruptibly() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + final BooleanLatch pleaseInterrupt = new BooleanLatch(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + sync.acquire(); + assertTrue(pleaseInterrupt.releaseShared(0)); + c.awaitUninterruptibly(); + assertTrue(Thread.interrupted()); + assertHasWaitersLocked(sync, c, NO_THREADS); + sync.release(); + }}); + + pleaseInterrupt.acquireShared(0); + sync.acquire(); + assertHasWaitersLocked(sync, c, t); + sync.release(); + t.interrupt(); + assertHasWaitersUnlocked(sync, c, t); + assertThreadStaysAlive(t); + sync.acquire(); + assertHasWaitersLocked(sync, c, t); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + c.signal(); + assertHasWaitersLocked(sync, c, NO_THREADS); + assertHasExclusiveQueuedThreads(sync, t); + sync.release(); + awaitTermination(t); + } + + /** + * await/awaitNanos/awaitUntil is interruptible + */ + public void testInterruptible_await() { testInterruptible(AwaitMethod.await); } + public void testInterruptible_awaitTimed() { testInterruptible(AwaitMethod.awaitTimed); } + public void testInterruptible_awaitNanos() { testInterruptible(AwaitMethod.awaitNanos); } + public void testInterruptible_awaitUntil() { testInterruptible(AwaitMethod.awaitUntil); } + public void testInterruptible(final AwaitMethod awaitMethod) { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + final BooleanLatch pleaseInterrupt = new BooleanLatch(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + sync.acquire(); + assertTrue(pleaseInterrupt.releaseShared(0)); + await(c, awaitMethod); + }}); + + pleaseInterrupt.acquireShared(0); + t.interrupt(); + awaitTermination(t); + } + + /** + * signalAll wakes up all threads + */ + public void testSignalAll_await() { testSignalAll(AwaitMethod.await); } + public void testSignalAll_awaitTimed() { testSignalAll(AwaitMethod.awaitTimed); } + public void testSignalAll_awaitNanos() { testSignalAll(AwaitMethod.awaitNanos); } + public void testSignalAll_awaitUntil() { testSignalAll(AwaitMethod.awaitUntil); } + public void testSignalAll(final AwaitMethod awaitMethod) { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + final BooleanLatch acquired1 = new BooleanLatch(); + final BooleanLatch acquired2 = new BooleanLatch(); + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + sync.acquire(); + acquired1.releaseShared(0); + await(c, awaitMethod); + sync.release(); + }}); + + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + sync.acquire(); + acquired2.releaseShared(0); + await(c, awaitMethod); + sync.release(); + }}); + + acquired1.acquireShared(0); + acquired2.acquireShared(0); + sync.acquire(); + assertHasWaitersLocked(sync, c, t1, t2); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + c.signalAll(); + assertHasWaitersLocked(sync, c, NO_THREADS); + assertHasExclusiveQueuedThreads(sync, t1, t2); + sync.release(); + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * toString indicates current state + */ + public void testToString() { + Mutex sync = new Mutex(); + assertTrue(sync.toString().contains("State = " + Mutex.UNLOCKED)); + sync.acquire(); + assertTrue(sync.toString().contains("State = " + Mutex.LOCKED)); + } + + /** + * A serialized AQS deserializes with current state, but no queued threads + */ + public void testSerialization() { + Mutex sync = new Mutex(); + assertFalse(serialClone(sync).isHeldExclusively()); + sync.acquire(); + Thread t = newStartedThread(new InterruptedSyncRunnable(sync)); + waitForQueuedThread(sync, t); + assertTrue(sync.isHeldExclusively()); + + Mutex clone = serialClone(sync); + assertTrue(clone.isHeldExclusively()); + assertHasExclusiveQueuedThreads(sync, t); + assertHasExclusiveQueuedThreads(clone, NO_THREADS); + t.interrupt(); + awaitTermination(t); + sync.release(); + assertFalse(sync.isHeldExclusively()); + assertTrue(clone.isHeldExclusively()); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + assertHasExclusiveQueuedThreads(clone, NO_THREADS); + } + + /** + * tryReleaseShared setting state changes getState + */ + public void testGetStateWithReleaseShared() { + final BooleanLatch l = new BooleanLatch(); + assertFalse(l.isSignalled()); + assertTrue(l.releaseShared(0)); + assertTrue(l.isSignalled()); + } + + /** + * releaseShared has no effect when already signalled + */ + public void testReleaseShared() { + final BooleanLatch l = new BooleanLatch(); + assertFalse(l.isSignalled()); + assertTrue(l.releaseShared(0)); + assertTrue(l.isSignalled()); + assertTrue(l.releaseShared(0)); + assertTrue(l.isSignalled()); + } + + /** + * acquireSharedInterruptibly returns after release, but not before + */ + public void testAcquireSharedInterruptibly() { + final BooleanLatch l = new BooleanLatch(); + + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(l.isSignalled()); + l.acquireSharedInterruptibly(0); + assertTrue(l.isSignalled()); + l.acquireSharedInterruptibly(0); + assertTrue(l.isSignalled()); + }}); + + waitForQueuedThread(l, t); + assertFalse(l.isSignalled()); + assertThreadStaysAlive(t); + assertHasSharedQueuedThreads(l, t); + assertTrue(l.releaseShared(0)); + assertTrue(l.isSignalled()); + awaitTermination(t); + } + + /** + * tryAcquireSharedNanos returns after release, but not before + */ + public void testTryAcquireSharedNanos() { + final BooleanLatch l = new BooleanLatch(); + + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(l.isSignalled()); + long nanos = MILLISECONDS.toNanos(2 * LONG_DELAY_MS); + assertTrue(l.tryAcquireSharedNanos(0, nanos)); + assertTrue(l.isSignalled()); + assertTrue(l.tryAcquireSharedNanos(0, nanos)); + assertTrue(l.isSignalled()); + }}); + + waitForQueuedThread(l, t); + assertFalse(l.isSignalled()); + assertThreadStaysAlive(t); + assertTrue(l.releaseShared(0)); + assertTrue(l.isSignalled()); + awaitTermination(t); + } + + /** + * acquireSharedInterruptibly is interruptible + */ + public void testAcquireSharedInterruptibly_Interruptible() { + final BooleanLatch l = new BooleanLatch(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(l.isSignalled()); + l.acquireSharedInterruptibly(0); + }}); + + waitForQueuedThread(l, t); + assertFalse(l.isSignalled()); + t.interrupt(); + awaitTermination(t); + assertFalse(l.isSignalled()); + } + + /** + * tryAcquireSharedNanos is interruptible + */ + public void testTryAcquireSharedNanos_Interruptible() { + final BooleanLatch l = new BooleanLatch(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(l.isSignalled()); + long nanos = MILLISECONDS.toNanos(2 * LONG_DELAY_MS); + l.tryAcquireSharedNanos(0, nanos); + }}); + + waitForQueuedThread(l, t); + assertFalse(l.isSignalled()); + t.interrupt(); + awaitTermination(t); + assertFalse(l.isSignalled()); + } + + /** + * tryAcquireSharedNanos times out if not released before timeout + */ + public void testTryAcquireSharedNanos_Timeout() { + final BooleanLatch l = new BooleanLatch(); + final BooleanLatch observedQueued = new BooleanLatch(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(l.isSignalled()); + for (long millis = timeoutMillis(); + !observedQueued.isSignalled(); + millis *= 2) { + long nanos = MILLISECONDS.toNanos(millis); + long startTime = System.nanoTime(); + assertFalse(l.tryAcquireSharedNanos(0, nanos)); + assertTrue(millisElapsedSince(startTime) >= millis); + } + assertFalse(l.isSignalled()); + }}); + + waitForQueuedThread(l, t); + observedQueued.releaseShared(0); + assertFalse(l.isSignalled()); + awaitTermination(t); + assertFalse(l.isSignalled()); + } + + /** + * awaitNanos/timed await with 0 wait times out immediately + */ + public void testAwait_Zero() throws InterruptedException { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + sync.acquire(); + assertTrue(c.awaitNanos(0L) <= 0); + assertFalse(c.await(0L, NANOSECONDS)); + sync.release(); + } + + /** + * awaitNanos/timed await with maximum negative wait times does not underflow + */ + public void testAwait_NegativeInfinity() throws InterruptedException { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + sync.acquire(); + assertTrue(c.awaitNanos(Long.MIN_VALUE) <= 0); + assertFalse(c.await(Long.MIN_VALUE, NANOSECONDS)); + sync.release(); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/AbstractQueuedSynchronizerTest.java b/jdk/test/java/util/concurrent/tck/AbstractQueuedSynchronizerTest.java new file mode 100644 index 00000000000..8341a7e296c --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/AbstractQueuedSynchronizerTest.java @@ -0,0 +1,1283 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.concurrent.locks.AbstractQueuedSynchronizer; +import java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AbstractQueuedSynchronizerTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(AbstractQueuedSynchronizerTest.class); + } + + /** + * A simple mutex class, adapted from the class javadoc. Exclusive + * acquire tests exercise this as a sample user extension. Other + * methods/features of AbstractQueuedSynchronizer are tested via + * other test classes, including those for ReentrantLock, + * ReentrantReadWriteLock, and Semaphore. + */ + static class Mutex extends AbstractQueuedSynchronizer { + /** An eccentric value for locked synchronizer state. */ + static final int LOCKED = (1 << 31) | (1 << 15); + + static final int UNLOCKED = 0; + + @Override public boolean isHeldExclusively() { + int state = getState(); + assertTrue(state == UNLOCKED || state == LOCKED); + return state == LOCKED; + } + + @Override public boolean tryAcquire(int acquires) { + assertEquals(LOCKED, acquires); + return compareAndSetState(UNLOCKED, LOCKED); + } + + @Override public boolean tryRelease(int releases) { + if (getState() != LOCKED) throw new IllegalMonitorStateException(); + assertEquals(LOCKED, releases); + setState(UNLOCKED); + return true; + } + + public boolean tryAcquireNanos(long nanos) throws InterruptedException { + return tryAcquireNanos(LOCKED, nanos); + } + + public boolean tryAcquire() { + return tryAcquire(LOCKED); + } + + public boolean tryRelease() { + return tryRelease(LOCKED); + } + + public void acquire() { + acquire(LOCKED); + } + + public void acquireInterruptibly() throws InterruptedException { + acquireInterruptibly(LOCKED); + } + + public void release() { + release(LOCKED); + } + + public ConditionObject newCondition() { + return new ConditionObject(); + } + } + + /** + * A simple latch class, to test shared mode. + */ + static class BooleanLatch extends AbstractQueuedSynchronizer { + public boolean isSignalled() { return getState() != 0; } + + public int tryAcquireShared(int ignore) { + return isSignalled() ? 1 : -1; + } + + public boolean tryReleaseShared(int ignore) { + setState(1); + return true; + } + } + + /** + * A runnable calling acquireInterruptibly that does not expect to + * be interrupted. + */ + class InterruptibleSyncRunnable extends CheckedRunnable { + final Mutex sync; + InterruptibleSyncRunnable(Mutex sync) { this.sync = sync; } + public void realRun() throws InterruptedException { + sync.acquireInterruptibly(); + } + } + + /** + * A runnable calling acquireInterruptibly that expects to be + * interrupted. + */ + class InterruptedSyncRunnable extends CheckedInterruptedRunnable { + final Mutex sync; + InterruptedSyncRunnable(Mutex sync) { this.sync = sync; } + public void realRun() throws InterruptedException { + sync.acquireInterruptibly(); + } + } + + /** A constant to clarify calls to checking methods below. */ + static final Thread[] NO_THREADS = new Thread[0]; + + /** + * Spin-waits until sync.isQueued(t) becomes true. + */ + void waitForQueuedThread(AbstractQueuedSynchronizer sync, Thread t) { + long startTime = System.nanoTime(); + while (!sync.isQueued(t)) { + if (millisElapsedSince(startTime) > LONG_DELAY_MS) + throw new AssertionFailedError("timed out"); + Thread.yield(); + } + assertTrue(t.isAlive()); + } + + /** + * Checks that sync has exactly the given queued threads. + */ + void assertHasQueuedThreads(AbstractQueuedSynchronizer sync, + Thread... expected) { + Collection actual = sync.getQueuedThreads(); + assertEquals(expected.length > 0, sync.hasQueuedThreads()); + assertEquals(expected.length, sync.getQueueLength()); + assertEquals(expected.length, actual.size()); + assertEquals(expected.length == 0, actual.isEmpty()); + assertEquals(new HashSet(actual), + new HashSet(Arrays.asList(expected))); + } + + /** + * Checks that sync has exactly the given (exclusive) queued threads. + */ + void assertHasExclusiveQueuedThreads(AbstractQueuedSynchronizer sync, + Thread... expected) { + assertHasQueuedThreads(sync, expected); + assertEquals(new HashSet(sync.getExclusiveQueuedThreads()), + new HashSet(sync.getQueuedThreads())); + assertEquals(0, sync.getSharedQueuedThreads().size()); + assertTrue(sync.getSharedQueuedThreads().isEmpty()); + } + + /** + * Checks that sync has exactly the given (shared) queued threads. + */ + void assertHasSharedQueuedThreads(AbstractQueuedSynchronizer sync, + Thread... expected) { + assertHasQueuedThreads(sync, expected); + assertEquals(new HashSet(sync.getSharedQueuedThreads()), + new HashSet(sync.getQueuedThreads())); + assertEquals(0, sync.getExclusiveQueuedThreads().size()); + assertTrue(sync.getExclusiveQueuedThreads().isEmpty()); + } + + /** + * Checks that condition c has exactly the given waiter threads, + * after acquiring mutex. + */ + void assertHasWaitersUnlocked(Mutex sync, ConditionObject c, + Thread... threads) { + sync.acquire(); + assertHasWaitersLocked(sync, c, threads); + sync.release(); + } + + /** + * Checks that condition c has exactly the given waiter threads. + */ + void assertHasWaitersLocked(Mutex sync, ConditionObject c, + Thread... threads) { + assertEquals(threads.length > 0, sync.hasWaiters(c)); + assertEquals(threads.length, sync.getWaitQueueLength(c)); + assertEquals(threads.length == 0, sync.getWaitingThreads(c).isEmpty()); + assertEquals(threads.length, sync.getWaitingThreads(c).size()); + assertEquals(new HashSet(sync.getWaitingThreads(c)), + new HashSet(Arrays.asList(threads))); + } + + enum AwaitMethod { await, awaitTimed, awaitNanos, awaitUntil } + + /** + * Awaits condition using the specified AwaitMethod. + */ + void await(ConditionObject c, AwaitMethod awaitMethod) + throws InterruptedException { + long timeoutMillis = 2 * LONG_DELAY_MS; + switch (awaitMethod) { + case await: + c.await(); + break; + case awaitTimed: + assertTrue(c.await(timeoutMillis, MILLISECONDS)); + break; + case awaitNanos: + long nanosTimeout = MILLISECONDS.toNanos(timeoutMillis); + long nanosRemaining = c.awaitNanos(nanosTimeout); + assertTrue(nanosRemaining > 0); + break; + case awaitUntil: + assertTrue(c.awaitUntil(delayedDate(timeoutMillis))); + break; + default: + throw new AssertionError(); + } + } + + /** + * Checks that awaiting the given condition times out (using the + * default timeout duration). + */ + void assertAwaitTimesOut(ConditionObject c, AwaitMethod awaitMethod) { + long timeoutMillis = timeoutMillis(); + long startTime; + try { + switch (awaitMethod) { + case awaitTimed: + startTime = System.nanoTime(); + assertFalse(c.await(timeoutMillis, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + break; + case awaitNanos: + startTime = System.nanoTime(); + long nanosTimeout = MILLISECONDS.toNanos(timeoutMillis); + long nanosRemaining = c.awaitNanos(nanosTimeout); + assertTrue(nanosRemaining <= 0); + assertTrue(nanosRemaining > -MILLISECONDS.toNanos(LONG_DELAY_MS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + break; + case awaitUntil: + // We shouldn't assume that nanoTime and currentTimeMillis + // use the same time source, so don't use nanoTime here. + java.util.Date delayedDate = delayedDate(timeoutMillis()); + assertFalse(c.awaitUntil(delayedDate(timeoutMillis))); + assertTrue(new java.util.Date().getTime() >= delayedDate.getTime()); + break; + default: + throw new UnsupportedOperationException(); + } + } catch (InterruptedException ie) { threadUnexpectedException(ie); } + } + + /** + * isHeldExclusively is false upon construction + */ + public void testIsHeldExclusively() { + Mutex sync = new Mutex(); + assertFalse(sync.isHeldExclusively()); + } + + /** + * acquiring released sync succeeds + */ + public void testAcquire() { + Mutex sync = new Mutex(); + sync.acquire(); + assertTrue(sync.isHeldExclusively()); + sync.release(); + assertFalse(sync.isHeldExclusively()); + } + + /** + * tryAcquire on a released sync succeeds + */ + public void testTryAcquire() { + Mutex sync = new Mutex(); + assertTrue(sync.tryAcquire()); + assertTrue(sync.isHeldExclusively()); + sync.release(); + assertFalse(sync.isHeldExclusively()); + } + + /** + * hasQueuedThreads reports whether there are waiting threads + */ + public void testHasQueuedThreads() { + final Mutex sync = new Mutex(); + assertFalse(sync.hasQueuedThreads()); + sync.acquire(); + Thread t1 = newStartedThread(new InterruptedSyncRunnable(sync)); + waitForQueuedThread(sync, t1); + assertTrue(sync.hasQueuedThreads()); + Thread t2 = newStartedThread(new InterruptibleSyncRunnable(sync)); + waitForQueuedThread(sync, t2); + assertTrue(sync.hasQueuedThreads()); + t1.interrupt(); + awaitTermination(t1); + assertTrue(sync.hasQueuedThreads()); + sync.release(); + awaitTermination(t2); + assertFalse(sync.hasQueuedThreads()); + } + + /** + * isQueued(null) throws NullPointerException + */ + public void testIsQueuedNPE() { + final Mutex sync = new Mutex(); + try { + sync.isQueued(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * isQueued reports whether a thread is queued + */ + public void testIsQueued() { + final Mutex sync = new Mutex(); + Thread t1 = new Thread(new InterruptedSyncRunnable(sync)); + Thread t2 = new Thread(new InterruptibleSyncRunnable(sync)); + assertFalse(sync.isQueued(t1)); + assertFalse(sync.isQueued(t2)); + sync.acquire(); + t1.start(); + waitForQueuedThread(sync, t1); + assertTrue(sync.isQueued(t1)); + assertFalse(sync.isQueued(t2)); + t2.start(); + waitForQueuedThread(sync, t2); + assertTrue(sync.isQueued(t1)); + assertTrue(sync.isQueued(t2)); + t1.interrupt(); + awaitTermination(t1); + assertFalse(sync.isQueued(t1)); + assertTrue(sync.isQueued(t2)); + sync.release(); + awaitTermination(t2); + assertFalse(sync.isQueued(t1)); + assertFalse(sync.isQueued(t2)); + } + + /** + * getFirstQueuedThread returns first waiting thread or null if none + */ + public void testGetFirstQueuedThread() { + final Mutex sync = new Mutex(); + assertNull(sync.getFirstQueuedThread()); + sync.acquire(); + Thread t1 = newStartedThread(new InterruptedSyncRunnable(sync)); + waitForQueuedThread(sync, t1); + assertEquals(t1, sync.getFirstQueuedThread()); + Thread t2 = newStartedThread(new InterruptibleSyncRunnable(sync)); + waitForQueuedThread(sync, t2); + assertEquals(t1, sync.getFirstQueuedThread()); + t1.interrupt(); + awaitTermination(t1); + assertEquals(t2, sync.getFirstQueuedThread()); + sync.release(); + awaitTermination(t2); + assertNull(sync.getFirstQueuedThread()); + } + + /** + * hasContended reports false if no thread has ever blocked, else true + */ + public void testHasContended() { + final Mutex sync = new Mutex(); + assertFalse(sync.hasContended()); + sync.acquire(); + assertFalse(sync.hasContended()); + Thread t1 = newStartedThread(new InterruptedSyncRunnable(sync)); + waitForQueuedThread(sync, t1); + assertTrue(sync.hasContended()); + Thread t2 = newStartedThread(new InterruptibleSyncRunnable(sync)); + waitForQueuedThread(sync, t2); + assertTrue(sync.hasContended()); + t1.interrupt(); + awaitTermination(t1); + assertTrue(sync.hasContended()); + sync.release(); + awaitTermination(t2); + assertTrue(sync.hasContended()); + } + + /** + * getQueuedThreads returns all waiting threads + */ + public void testGetQueuedThreads() { + final Mutex sync = new Mutex(); + Thread t1 = new Thread(new InterruptedSyncRunnable(sync)); + Thread t2 = new Thread(new InterruptibleSyncRunnable(sync)); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + sync.acquire(); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + t1.start(); + waitForQueuedThread(sync, t1); + assertHasExclusiveQueuedThreads(sync, t1); + assertTrue(sync.getQueuedThreads().contains(t1)); + assertFalse(sync.getQueuedThreads().contains(t2)); + t2.start(); + waitForQueuedThread(sync, t2); + assertHasExclusiveQueuedThreads(sync, t1, t2); + assertTrue(sync.getQueuedThreads().contains(t1)); + assertTrue(sync.getQueuedThreads().contains(t2)); + t1.interrupt(); + awaitTermination(t1); + assertHasExclusiveQueuedThreads(sync, t2); + sync.release(); + awaitTermination(t2); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + } + + /** + * getExclusiveQueuedThreads returns all exclusive waiting threads + */ + public void testGetExclusiveQueuedThreads() { + final Mutex sync = new Mutex(); + Thread t1 = new Thread(new InterruptedSyncRunnable(sync)); + Thread t2 = new Thread(new InterruptibleSyncRunnable(sync)); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + sync.acquire(); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + t1.start(); + waitForQueuedThread(sync, t1); + assertHasExclusiveQueuedThreads(sync, t1); + assertTrue(sync.getExclusiveQueuedThreads().contains(t1)); + assertFalse(sync.getExclusiveQueuedThreads().contains(t2)); + t2.start(); + waitForQueuedThread(sync, t2); + assertHasExclusiveQueuedThreads(sync, t1, t2); + assertTrue(sync.getExclusiveQueuedThreads().contains(t1)); + assertTrue(sync.getExclusiveQueuedThreads().contains(t2)); + t1.interrupt(); + awaitTermination(t1); + assertHasExclusiveQueuedThreads(sync, t2); + sync.release(); + awaitTermination(t2); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + } + + /** + * getSharedQueuedThreads does not include exclusively waiting threads + */ + public void testGetSharedQueuedThreads_Exclusive() { + final Mutex sync = new Mutex(); + assertTrue(sync.getSharedQueuedThreads().isEmpty()); + sync.acquire(); + assertTrue(sync.getSharedQueuedThreads().isEmpty()); + Thread t1 = newStartedThread(new InterruptedSyncRunnable(sync)); + waitForQueuedThread(sync, t1); + assertTrue(sync.getSharedQueuedThreads().isEmpty()); + Thread t2 = newStartedThread(new InterruptibleSyncRunnable(sync)); + waitForQueuedThread(sync, t2); + assertTrue(sync.getSharedQueuedThreads().isEmpty()); + t1.interrupt(); + awaitTermination(t1); + assertTrue(sync.getSharedQueuedThreads().isEmpty()); + sync.release(); + awaitTermination(t2); + assertTrue(sync.getSharedQueuedThreads().isEmpty()); + } + + /** + * getSharedQueuedThreads returns all shared waiting threads + */ + public void testGetSharedQueuedThreads_Shared() { + final BooleanLatch l = new BooleanLatch(); + assertHasSharedQueuedThreads(l, NO_THREADS); + Thread t1 = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + l.acquireSharedInterruptibly(0); + }}); + waitForQueuedThread(l, t1); + assertHasSharedQueuedThreads(l, t1); + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + l.acquireSharedInterruptibly(0); + }}); + waitForQueuedThread(l, t2); + assertHasSharedQueuedThreads(l, t1, t2); + t1.interrupt(); + awaitTermination(t1); + assertHasSharedQueuedThreads(l, t2); + assertTrue(l.releaseShared(0)); + awaitTermination(t2); + assertHasSharedQueuedThreads(l, NO_THREADS); + } + + /** + * tryAcquireNanos is interruptible + */ + public void testTryAcquireNanos_Interruptible() { + final Mutex sync = new Mutex(); + sync.acquire(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + sync.tryAcquireNanos(MILLISECONDS.toNanos(2 * LONG_DELAY_MS)); + }}); + + waitForQueuedThread(sync, t); + t.interrupt(); + awaitTermination(t); + } + + /** + * tryAcquire on exclusively held sync fails + */ + public void testTryAcquireWhenSynced() { + final Mutex sync = new Mutex(); + sync.acquire(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + assertFalse(sync.tryAcquire()); + }}); + + awaitTermination(t); + sync.release(); + } + + /** + * tryAcquireNanos on an exclusively held sync times out + */ + public void testAcquireNanos_Timeout() { + final Mutex sync = new Mutex(); + sync.acquire(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + long nanos = MILLISECONDS.toNanos(timeoutMillis()); + assertFalse(sync.tryAcquireNanos(nanos)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + }}); + + awaitTermination(t); + sync.release(); + } + + /** + * getState is true when acquired and false when not + */ + public void testGetState() { + final Mutex sync = new Mutex(); + sync.acquire(); + assertTrue(sync.isHeldExclusively()); + sync.release(); + assertFalse(sync.isHeldExclusively()); + + final BooleanLatch acquired = new BooleanLatch(); + final BooleanLatch done = new BooleanLatch(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + sync.acquire(); + assertTrue(acquired.releaseShared(0)); + done.acquireShared(0); + sync.release(); + }}); + + acquired.acquireShared(0); + assertTrue(sync.isHeldExclusively()); + assertTrue(done.releaseShared(0)); + awaitTermination(t); + assertFalse(sync.isHeldExclusively()); + } + + /** + * acquireInterruptibly succeeds when released, else is interruptible + */ + public void testAcquireInterruptibly() throws InterruptedException { + final Mutex sync = new Mutex(); + final BooleanLatch threadStarted = new BooleanLatch(); + sync.acquireInterruptibly(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + assertTrue(threadStarted.releaseShared(0)); + sync.acquireInterruptibly(); + }}); + + threadStarted.acquireShared(0); + waitForQueuedThread(sync, t); + t.interrupt(); + awaitTermination(t); + assertTrue(sync.isHeldExclusively()); + } + + /** + * owns is true for a condition created by sync else false + */ + public void testOwns() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + final Mutex sync2 = new Mutex(); + assertTrue(sync.owns(c)); + assertFalse(sync2.owns(c)); + } + + /** + * Calling await without holding sync throws IllegalMonitorStateException + */ + public void testAwait_IMSE() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + for (AwaitMethod awaitMethod : AwaitMethod.values()) { + long startTime = System.nanoTime(); + try { + await(c, awaitMethod); + shouldThrow(); + } catch (IllegalMonitorStateException success) { + } catch (InterruptedException e) { threadUnexpectedException(e); } + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + } + + /** + * Calling signal without holding sync throws IllegalMonitorStateException + */ + public void testSignal_IMSE() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + try { + c.signal(); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + assertHasWaitersUnlocked(sync, c, NO_THREADS); + } + + /** + * Calling signalAll without holding sync throws IllegalMonitorStateException + */ + public void testSignalAll_IMSE() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + try { + c.signalAll(); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * await/awaitNanos/awaitUntil without a signal times out + */ + public void testAwaitTimed_Timeout() { testAwait_Timeout(AwaitMethod.awaitTimed); } + public void testAwaitNanos_Timeout() { testAwait_Timeout(AwaitMethod.awaitNanos); } + public void testAwaitUntil_Timeout() { testAwait_Timeout(AwaitMethod.awaitUntil); } + public void testAwait_Timeout(AwaitMethod awaitMethod) { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + sync.acquire(); + assertAwaitTimesOut(c, awaitMethod); + sync.release(); + } + + /** + * await/awaitNanos/awaitUntil returns when signalled + */ + public void testSignal_await() { testSignal(AwaitMethod.await); } + public void testSignal_awaitTimed() { testSignal(AwaitMethod.awaitTimed); } + public void testSignal_awaitNanos() { testSignal(AwaitMethod.awaitNanos); } + public void testSignal_awaitUntil() { testSignal(AwaitMethod.awaitUntil); } + public void testSignal(final AwaitMethod awaitMethod) { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + final BooleanLatch acquired = new BooleanLatch(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + sync.acquire(); + assertTrue(acquired.releaseShared(0)); + await(c, awaitMethod); + sync.release(); + }}); + + acquired.acquireShared(0); + sync.acquire(); + assertHasWaitersLocked(sync, c, t); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + c.signal(); + assertHasWaitersLocked(sync, c, NO_THREADS); + assertHasExclusiveQueuedThreads(sync, t); + sync.release(); + awaitTermination(t); + } + + /** + * hasWaiters(null) throws NullPointerException + */ + public void testHasWaitersNPE() { + final Mutex sync = new Mutex(); + try { + sync.hasWaiters(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * getWaitQueueLength(null) throws NullPointerException + */ + public void testGetWaitQueueLengthNPE() { + final Mutex sync = new Mutex(); + try { + sync.getWaitQueueLength(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * getWaitingThreads(null) throws NullPointerException + */ + public void testGetWaitingThreadsNPE() { + final Mutex sync = new Mutex(); + try { + sync.getWaitingThreads(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * hasWaiters throws IllegalArgumentException if not owned + */ + public void testHasWaitersIAE() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + final Mutex sync2 = new Mutex(); + try { + sync2.hasWaiters(c); + shouldThrow(); + } catch (IllegalArgumentException success) {} + assertHasWaitersUnlocked(sync, c, NO_THREADS); + } + + /** + * hasWaiters throws IllegalMonitorStateException if not synced + */ + public void testHasWaitersIMSE() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + try { + sync.hasWaiters(c); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + assertHasWaitersUnlocked(sync, c, NO_THREADS); + } + + /** + * getWaitQueueLength throws IllegalArgumentException if not owned + */ + public void testGetWaitQueueLengthIAE() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + final Mutex sync2 = new Mutex(); + try { + sync2.getWaitQueueLength(c); + shouldThrow(); + } catch (IllegalArgumentException success) {} + assertHasWaitersUnlocked(sync, c, NO_THREADS); + } + + /** + * getWaitQueueLength throws IllegalMonitorStateException if not synced + */ + public void testGetWaitQueueLengthIMSE() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + try { + sync.getWaitQueueLength(c); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + assertHasWaitersUnlocked(sync, c, NO_THREADS); + } + + /** + * getWaitingThreads throws IllegalArgumentException if not owned + */ + public void testGetWaitingThreadsIAE() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + final Mutex sync2 = new Mutex(); + try { + sync2.getWaitingThreads(c); + shouldThrow(); + } catch (IllegalArgumentException success) {} + assertHasWaitersUnlocked(sync, c, NO_THREADS); + } + + /** + * getWaitingThreads throws IllegalMonitorStateException if not synced + */ + public void testGetWaitingThreadsIMSE() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + try { + sync.getWaitingThreads(c); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + assertHasWaitersUnlocked(sync, c, NO_THREADS); + } + + /** + * hasWaiters returns true when a thread is waiting, else false + */ + public void testHasWaiters() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + final BooleanLatch acquired = new BooleanLatch(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + sync.acquire(); + assertHasWaitersLocked(sync, c, NO_THREADS); + assertFalse(sync.hasWaiters(c)); + assertTrue(acquired.releaseShared(0)); + c.await(); + sync.release(); + }}); + + acquired.acquireShared(0); + sync.acquire(); + assertHasWaitersLocked(sync, c, t); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + assertTrue(sync.hasWaiters(c)); + c.signal(); + assertHasWaitersLocked(sync, c, NO_THREADS); + assertHasExclusiveQueuedThreads(sync, t); + assertFalse(sync.hasWaiters(c)); + sync.release(); + + awaitTermination(t); + assertHasWaitersUnlocked(sync, c, NO_THREADS); + } + + /** + * getWaitQueueLength returns number of waiting threads + */ + public void testGetWaitQueueLength() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + final BooleanLatch acquired1 = new BooleanLatch(); + final BooleanLatch acquired2 = new BooleanLatch(); + final Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + sync.acquire(); + assertHasWaitersLocked(sync, c, NO_THREADS); + assertEquals(0, sync.getWaitQueueLength(c)); + assertTrue(acquired1.releaseShared(0)); + c.await(); + sync.release(); + }}); + acquired1.acquireShared(0); + sync.acquire(); + assertHasWaitersLocked(sync, c, t1); + assertEquals(1, sync.getWaitQueueLength(c)); + sync.release(); + + final Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + sync.acquire(); + assertHasWaitersLocked(sync, c, t1); + assertEquals(1, sync.getWaitQueueLength(c)); + assertTrue(acquired2.releaseShared(0)); + c.await(); + sync.release(); + }}); + acquired2.acquireShared(0); + sync.acquire(); + assertHasWaitersLocked(sync, c, t1, t2); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + assertEquals(2, sync.getWaitQueueLength(c)); + c.signalAll(); + assertHasWaitersLocked(sync, c, NO_THREADS); + assertHasExclusiveQueuedThreads(sync, t1, t2); + assertEquals(0, sync.getWaitQueueLength(c)); + sync.release(); + + awaitTermination(t1); + awaitTermination(t2); + assertHasWaitersUnlocked(sync, c, NO_THREADS); + } + + /** + * getWaitingThreads returns only and all waiting threads + */ + public void testGetWaitingThreads() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + final BooleanLatch acquired1 = new BooleanLatch(); + final BooleanLatch acquired2 = new BooleanLatch(); + final Thread t1 = new Thread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + sync.acquire(); + assertHasWaitersLocked(sync, c, NO_THREADS); + assertTrue(sync.getWaitingThreads(c).isEmpty()); + assertTrue(acquired1.releaseShared(0)); + c.await(); + sync.release(); + }}); + + final Thread t2 = new Thread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + sync.acquire(); + assertHasWaitersLocked(sync, c, t1); + assertTrue(sync.getWaitingThreads(c).contains(t1)); + assertFalse(sync.getWaitingThreads(c).isEmpty()); + assertEquals(1, sync.getWaitingThreads(c).size()); + assertTrue(acquired2.releaseShared(0)); + c.await(); + sync.release(); + }}); + + sync.acquire(); + assertHasWaitersLocked(sync, c, NO_THREADS); + assertFalse(sync.getWaitingThreads(c).contains(t1)); + assertFalse(sync.getWaitingThreads(c).contains(t2)); + assertTrue(sync.getWaitingThreads(c).isEmpty()); + assertEquals(0, sync.getWaitingThreads(c).size()); + sync.release(); + + t1.start(); + acquired1.acquireShared(0); + sync.acquire(); + assertHasWaitersLocked(sync, c, t1); + assertTrue(sync.getWaitingThreads(c).contains(t1)); + assertFalse(sync.getWaitingThreads(c).contains(t2)); + assertFalse(sync.getWaitingThreads(c).isEmpty()); + assertEquals(1, sync.getWaitingThreads(c).size()); + sync.release(); + + t2.start(); + acquired2.acquireShared(0); + sync.acquire(); + assertHasWaitersLocked(sync, c, t1, t2); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + assertTrue(sync.getWaitingThreads(c).contains(t1)); + assertTrue(sync.getWaitingThreads(c).contains(t2)); + assertFalse(sync.getWaitingThreads(c).isEmpty()); + assertEquals(2, sync.getWaitingThreads(c).size()); + c.signalAll(); + assertHasWaitersLocked(sync, c, NO_THREADS); + assertHasExclusiveQueuedThreads(sync, t1, t2); + assertFalse(sync.getWaitingThreads(c).contains(t1)); + assertFalse(sync.getWaitingThreads(c).contains(t2)); + assertTrue(sync.getWaitingThreads(c).isEmpty()); + assertEquals(0, sync.getWaitingThreads(c).size()); + sync.release(); + + awaitTermination(t1); + awaitTermination(t2); + assertHasWaitersUnlocked(sync, c, NO_THREADS); + } + + /** + * awaitUninterruptibly is uninterruptible + */ + public void testAwaitUninterruptibly() { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + final BooleanLatch pleaseInterrupt = new BooleanLatch(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + sync.acquire(); + assertTrue(pleaseInterrupt.releaseShared(0)); + c.awaitUninterruptibly(); + assertTrue(Thread.interrupted()); + assertHasWaitersLocked(sync, c, NO_THREADS); + sync.release(); + }}); + + pleaseInterrupt.acquireShared(0); + sync.acquire(); + assertHasWaitersLocked(sync, c, t); + sync.release(); + t.interrupt(); + assertHasWaitersUnlocked(sync, c, t); + assertThreadStaysAlive(t); + sync.acquire(); + assertHasWaitersLocked(sync, c, t); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + c.signal(); + assertHasWaitersLocked(sync, c, NO_THREADS); + assertHasExclusiveQueuedThreads(sync, t); + sync.release(); + awaitTermination(t); + } + + /** + * await/awaitNanos/awaitUntil is interruptible + */ + public void testInterruptible_await() { testInterruptible(AwaitMethod.await); } + public void testInterruptible_awaitTimed() { testInterruptible(AwaitMethod.awaitTimed); } + public void testInterruptible_awaitNanos() { testInterruptible(AwaitMethod.awaitNanos); } + public void testInterruptible_awaitUntil() { testInterruptible(AwaitMethod.awaitUntil); } + public void testInterruptible(final AwaitMethod awaitMethod) { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + final BooleanLatch pleaseInterrupt = new BooleanLatch(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + sync.acquire(); + assertTrue(pleaseInterrupt.releaseShared(0)); + await(c, awaitMethod); + }}); + + pleaseInterrupt.acquireShared(0); + t.interrupt(); + awaitTermination(t); + } + + /** + * signalAll wakes up all threads + */ + public void testSignalAll_await() { testSignalAll(AwaitMethod.await); } + public void testSignalAll_awaitTimed() { testSignalAll(AwaitMethod.awaitTimed); } + public void testSignalAll_awaitNanos() { testSignalAll(AwaitMethod.awaitNanos); } + public void testSignalAll_awaitUntil() { testSignalAll(AwaitMethod.awaitUntil); } + public void testSignalAll(final AwaitMethod awaitMethod) { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + final BooleanLatch acquired1 = new BooleanLatch(); + final BooleanLatch acquired2 = new BooleanLatch(); + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + sync.acquire(); + acquired1.releaseShared(0); + await(c, awaitMethod); + sync.release(); + }}); + + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + sync.acquire(); + acquired2.releaseShared(0); + await(c, awaitMethod); + sync.release(); + }}); + + acquired1.acquireShared(0); + acquired2.acquireShared(0); + sync.acquire(); + assertHasWaitersLocked(sync, c, t1, t2); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + c.signalAll(); + assertHasWaitersLocked(sync, c, NO_THREADS); + assertHasExclusiveQueuedThreads(sync, t1, t2); + sync.release(); + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * toString indicates current state + */ + public void testToString() { + Mutex sync = new Mutex(); + assertTrue(sync.toString().contains("State = " + Mutex.UNLOCKED)); + sync.acquire(); + assertTrue(sync.toString().contains("State = " + Mutex.LOCKED)); + } + + /** + * A serialized AQS deserializes with current state, but no queued threads + */ + public void testSerialization() { + Mutex sync = new Mutex(); + assertFalse(serialClone(sync).isHeldExclusively()); + sync.acquire(); + Thread t = newStartedThread(new InterruptedSyncRunnable(sync)); + waitForQueuedThread(sync, t); + assertTrue(sync.isHeldExclusively()); + + Mutex clone = serialClone(sync); + assertTrue(clone.isHeldExclusively()); + assertHasExclusiveQueuedThreads(sync, t); + assertHasExclusiveQueuedThreads(clone, NO_THREADS); + t.interrupt(); + awaitTermination(t); + sync.release(); + assertFalse(sync.isHeldExclusively()); + assertTrue(clone.isHeldExclusively()); + assertHasExclusiveQueuedThreads(sync, NO_THREADS); + assertHasExclusiveQueuedThreads(clone, NO_THREADS); + } + + /** + * tryReleaseShared setting state changes getState + */ + public void testGetStateWithReleaseShared() { + final BooleanLatch l = new BooleanLatch(); + assertFalse(l.isSignalled()); + assertTrue(l.releaseShared(0)); + assertTrue(l.isSignalled()); + } + + /** + * releaseShared has no effect when already signalled + */ + public void testReleaseShared() { + final BooleanLatch l = new BooleanLatch(); + assertFalse(l.isSignalled()); + assertTrue(l.releaseShared(0)); + assertTrue(l.isSignalled()); + assertTrue(l.releaseShared(0)); + assertTrue(l.isSignalled()); + } + + /** + * acquireSharedInterruptibly returns after release, but not before + */ + public void testAcquireSharedInterruptibly() { + final BooleanLatch l = new BooleanLatch(); + + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(l.isSignalled()); + l.acquireSharedInterruptibly(0); + assertTrue(l.isSignalled()); + l.acquireSharedInterruptibly(0); + assertTrue(l.isSignalled()); + }}); + + waitForQueuedThread(l, t); + assertFalse(l.isSignalled()); + assertThreadStaysAlive(t); + assertHasSharedQueuedThreads(l, t); + assertTrue(l.releaseShared(0)); + assertTrue(l.isSignalled()); + awaitTermination(t); + } + + /** + * tryAcquireSharedNanos returns after release, but not before + */ + public void testTryAcquireSharedNanos() { + final BooleanLatch l = new BooleanLatch(); + + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(l.isSignalled()); + long nanos = MILLISECONDS.toNanos(2 * LONG_DELAY_MS); + assertTrue(l.tryAcquireSharedNanos(0, nanos)); + assertTrue(l.isSignalled()); + assertTrue(l.tryAcquireSharedNanos(0, nanos)); + assertTrue(l.isSignalled()); + }}); + + waitForQueuedThread(l, t); + assertFalse(l.isSignalled()); + assertThreadStaysAlive(t); + assertTrue(l.releaseShared(0)); + assertTrue(l.isSignalled()); + awaitTermination(t); + } + + /** + * acquireSharedInterruptibly is interruptible + */ + public void testAcquireSharedInterruptibly_Interruptible() { + final BooleanLatch l = new BooleanLatch(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(l.isSignalled()); + l.acquireSharedInterruptibly(0); + }}); + + waitForQueuedThread(l, t); + assertFalse(l.isSignalled()); + t.interrupt(); + awaitTermination(t); + assertFalse(l.isSignalled()); + } + + /** + * tryAcquireSharedNanos is interruptible + */ + public void testTryAcquireSharedNanos_Interruptible() { + final BooleanLatch l = new BooleanLatch(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(l.isSignalled()); + long nanos = MILLISECONDS.toNanos(2 * LONG_DELAY_MS); + l.tryAcquireSharedNanos(0, nanos); + }}); + + waitForQueuedThread(l, t); + assertFalse(l.isSignalled()); + t.interrupt(); + awaitTermination(t); + assertFalse(l.isSignalled()); + } + + /** + * tryAcquireSharedNanos times out if not released before timeout + */ + public void testTryAcquireSharedNanos_Timeout() { + final BooleanLatch l = new BooleanLatch(); + final BooleanLatch observedQueued = new BooleanLatch(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(l.isSignalled()); + for (long millis = timeoutMillis(); + !observedQueued.isSignalled(); + millis *= 2) { + long nanos = MILLISECONDS.toNanos(millis); + long startTime = System.nanoTime(); + assertFalse(l.tryAcquireSharedNanos(0, nanos)); + assertTrue(millisElapsedSince(startTime) >= millis); + } + assertFalse(l.isSignalled()); + }}); + + waitForQueuedThread(l, t); + observedQueued.releaseShared(0); + assertFalse(l.isSignalled()); + awaitTermination(t); + assertFalse(l.isSignalled()); + } + + /** + * awaitNanos/timed await with 0 wait times out immediately + */ + public void testAwait_Zero() throws InterruptedException { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + sync.acquire(); + assertTrue(c.awaitNanos(0L) <= 0); + assertFalse(c.await(0L, NANOSECONDS)); + sync.release(); + } + + /** + * awaitNanos/timed await with maximum negative wait times does not underflow + */ + public void testAwait_NegativeInfinity() throws InterruptedException { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + sync.acquire(); + assertTrue(c.awaitNanos(Long.MIN_VALUE) <= 0); + assertFalse(c.await(Long.MIN_VALUE, NANOSECONDS)); + sync.release(); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/ArrayBlockingQueueTest.java b/jdk/test/java/util/concurrent/tck/ArrayBlockingQueueTest.java new file mode 100644 index 00000000000..e7fa0316197 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ArrayBlockingQueueTest.java @@ -0,0 +1,955 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; + +import junit.framework.Test; + +public class ArrayBlockingQueueTest extends JSR166TestCase { + + public static class Fair extends BlockingQueueTest { + protected BlockingQueue emptyCollection() { + return new ArrayBlockingQueue(SIZE, true); + } + } + + public static class NonFair extends BlockingQueueTest { + protected BlockingQueue emptyCollection() { + return new ArrayBlockingQueue(SIZE, false); + } + } + + public static void main(String[] args) { + main(suite(), args); + } + + public static Test suite() { + return newTestSuite(ArrayBlockingQueueTest.class, + new Fair().testSuite(), + new NonFair().testSuite()); + } + + /** + * Returns a new queue of given size containing consecutive + * Integers 0 ... n. + */ + private ArrayBlockingQueue populatedQueue(int n) { + ArrayBlockingQueue q = new ArrayBlockingQueue(n); + assertTrue(q.isEmpty()); + for (int i = 0; i < n; i++) + assertTrue(q.offer(new Integer(i))); + assertFalse(q.isEmpty()); + assertEquals(0, q.remainingCapacity()); + assertEquals(n, q.size()); + return q; + } + + /** + * A new queue has the indicated capacity + */ + public void testConstructor1() { + assertEquals(SIZE, new ArrayBlockingQueue(SIZE).remainingCapacity()); + } + + /** + * Constructor throws IAE if capacity argument nonpositive + */ + public void testConstructor2() { + try { + new ArrayBlockingQueue(0); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Initializing from null Collection throws NPE + */ + public void testConstructor3() { + try { + new ArrayBlockingQueue(1, true, null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing from Collection of null elements throws NPE + */ + public void testConstructor4() { + Collection elements = Arrays.asList(new Integer[SIZE]); + try { + new ArrayBlockingQueue(SIZE, false, elements); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing from Collection with some null elements throws NPE + */ + public void testConstructor5() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = i; + Collection elements = Arrays.asList(ints); + try { + new ArrayBlockingQueue(SIZE, false, elements); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing from too large collection throws IAE + */ + public void testConstructor6() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = i; + Collection elements = Arrays.asList(ints); + try { + new ArrayBlockingQueue(SIZE - 1, false, elements); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Queue contains all elements of collection used to initialize + */ + public void testConstructor7() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = i; + Collection elements = Arrays.asList(ints); + ArrayBlockingQueue q = new ArrayBlockingQueue(SIZE, true, elements); + for (int i = 0; i < SIZE; ++i) + assertEquals(ints[i], q.poll()); + } + + /** + * Queue transitions from empty to full when elements added + */ + public void testEmptyFull() { + ArrayBlockingQueue q = new ArrayBlockingQueue(2); + assertTrue(q.isEmpty()); + assertEquals(2, q.remainingCapacity()); + q.add(one); + assertFalse(q.isEmpty()); + q.add(two); + assertFalse(q.isEmpty()); + assertEquals(0, q.remainingCapacity()); + assertFalse(q.offer(three)); + } + + /** + * remainingCapacity decreases on add, increases on remove + */ + public void testRemainingCapacity() { + BlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.remainingCapacity()); + assertEquals(SIZE, q.size() + q.remainingCapacity()); + assertEquals(i, q.remove()); + } + for (int i = 0; i < SIZE; ++i) { + assertEquals(SIZE - i, q.remainingCapacity()); + assertEquals(SIZE, q.size() + q.remainingCapacity()); + assertTrue(q.add(i)); + } + } + + /** + * Offer succeeds if not full; fails if full + */ + public void testOffer() { + ArrayBlockingQueue q = new ArrayBlockingQueue(1); + assertTrue(q.offer(zero)); + assertFalse(q.offer(one)); + } + + /** + * add succeeds if not full; throws ISE if full + */ + public void testAdd() { + ArrayBlockingQueue q = new ArrayBlockingQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.add(new Integer(i))); + } + assertEquals(0, q.remainingCapacity()); + try { + q.add(new Integer(SIZE)); + shouldThrow(); + } catch (IllegalStateException success) {} + } + + /** + * addAll(this) throws IAE + */ + public void testAddAllSelf() { + ArrayBlockingQueue q = populatedQueue(SIZE); + try { + q.addAll(q); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * addAll of a collection with any null elements throws NPE after + * possibly adding some elements + */ + public void testAddAll3() { + ArrayBlockingQueue q = new ArrayBlockingQueue(SIZE); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); + try { + q.addAll(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll throws ISE if not enough room + */ + public void testAddAll4() { + ArrayBlockingQueue q = new ArrayBlockingQueue(1); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(i); + try { + q.addAll(Arrays.asList(ints)); + shouldThrow(); + } catch (IllegalStateException success) {} + } + + /** + * Queue contains all elements, in traversal order, of successful addAll + */ + public void testAddAll5() { + Integer[] empty = new Integer[0]; + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(i); + ArrayBlockingQueue q = new ArrayBlockingQueue(SIZE); + assertFalse(q.addAll(Arrays.asList(empty))); + assertTrue(q.addAll(Arrays.asList(ints))); + for (int i = 0; i < SIZE; ++i) + assertEquals(ints[i], q.poll()); + } + + /** + * all elements successfully put are contained + */ + public void testPut() throws InterruptedException { + ArrayBlockingQueue q = new ArrayBlockingQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + Integer x = new Integer(i); + q.put(x); + assertTrue(q.contains(x)); + } + assertEquals(0, q.remainingCapacity()); + } + + /** + * put blocks interruptibly if full + */ + public void testBlockingPut() throws InterruptedException { + final ArrayBlockingQueue q = new ArrayBlockingQueue(SIZE); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + for (int i = 0; i < SIZE; ++i) + q.put(i); + assertEquals(SIZE, q.size()); + assertEquals(0, q.remainingCapacity()); + + Thread.currentThread().interrupt(); + try { + q.put(99); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + q.put(99); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + assertEquals(SIZE, q.size()); + assertEquals(0, q.remainingCapacity()); + } + + /** + * put blocks interruptibly waiting for take when full + */ + public void testPutWithTake() throws InterruptedException { + final int capacity = 2; + final ArrayBlockingQueue q = new ArrayBlockingQueue(capacity); + final CountDownLatch pleaseTake = new CountDownLatch(1); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + for (int i = 0; i < capacity; i++) + q.put(i); + pleaseTake.countDown(); + q.put(86); + + pleaseInterrupt.countDown(); + try { + q.put(99); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseTake); + assertEquals(0, q.remainingCapacity()); + assertEquals(0, q.take()); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + assertEquals(0, q.remainingCapacity()); + } + + /** + * timed offer times out if full and elements not taken + */ + public void testTimedOffer() throws InterruptedException { + final ArrayBlockingQueue q = new ArrayBlockingQueue(2); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + q.put(new Object()); + q.put(new Object()); + long startTime = System.nanoTime(); + assertFalse(q.offer(new Object(), timeoutMillis(), MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + pleaseInterrupt.countDown(); + try { + q.offer(new Object(), 2 * LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * take retrieves elements in FIFO order + */ + public void testTake() throws InterruptedException { + ArrayBlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.take()); + } + } + + /** + * Take removes existing elements until empty, then blocks interruptibly + */ + public void testBlockingTake() throws InterruptedException { + final ArrayBlockingQueue q = populatedQueue(SIZE); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.take()); + } + + Thread.currentThread().interrupt(); + try { + q.take(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + q.take(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * poll succeeds unless empty + */ + public void testPoll() { + ArrayBlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.poll()); + } + assertNull(q.poll()); + } + + /** + * timed poll with zero timeout succeeds when non-empty, else times out + */ + public void testTimedPoll0() throws InterruptedException { + ArrayBlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.poll(0, MILLISECONDS)); + } + assertNull(q.poll(0, MILLISECONDS)); + checkEmpty(q); + } + + /** + * timed poll with nonzero timeout succeeds when non-empty, else times out + */ + public void testTimedPoll() throws InterruptedException { + ArrayBlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + long startTime = System.nanoTime(); + assertEquals(i, q.poll(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + long startTime = System.nanoTime(); + assertNull(q.poll(timeoutMillis(), MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + checkEmpty(q); + } + + /** + * Interrupted timed poll throws InterruptedException instead of + * returning timeout status + */ + public void testInterruptedTimedPoll() throws InterruptedException { + final BlockingQueue q = populatedQueue(SIZE); + final CountDownLatch aboutToWait = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, (int) q.poll(LONG_DELAY_MS, MILLISECONDS)); + } + aboutToWait.countDown(); + try { + q.poll(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) { + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + }}); + + await(aboutToWait); + waitForThreadToEnterWaitState(t, LONG_DELAY_MS); + t.interrupt(); + awaitTermination(t); + checkEmpty(q); + } + + /** + * peek returns next element, or null if empty + */ + public void testPeek() { + ArrayBlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.peek()); + assertEquals(i, q.poll()); + assertTrue(q.peek() == null || + !q.peek().equals(i)); + } + assertNull(q.peek()); + } + + /** + * element returns next element, or throws NSEE if empty + */ + public void testElement() { + ArrayBlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.element()); + assertEquals(i, q.poll()); + } + try { + q.element(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * remove removes next element, or throws NSEE if empty + */ + public void testRemove() { + ArrayBlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.remove()); + } + try { + q.remove(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * contains(x) reports true when elements added but not yet removed + */ + public void testContains() { + ArrayBlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.contains(new Integer(i))); + assertEquals(i, q.poll()); + assertFalse(q.contains(new Integer(i))); + } + } + + /** + * clear removes all elements + */ + public void testClear() { + ArrayBlockingQueue q = populatedQueue(SIZE); + q.clear(); + assertTrue(q.isEmpty()); + assertEquals(0, q.size()); + assertEquals(SIZE, q.remainingCapacity()); + q.add(one); + assertFalse(q.isEmpty()); + assertTrue(q.contains(one)); + q.clear(); + assertTrue(q.isEmpty()); + } + + /** + * containsAll(c) is true when c contains a subset of elements + */ + public void testContainsAll() { + ArrayBlockingQueue q = populatedQueue(SIZE); + ArrayBlockingQueue p = new ArrayBlockingQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.containsAll(p)); + assertFalse(p.containsAll(q)); + p.add(new Integer(i)); + } + assertTrue(p.containsAll(q)); + } + + /** + * retainAll(c) retains only those elements of c and reports true if changed + */ + public void testRetainAll() { + ArrayBlockingQueue q = populatedQueue(SIZE); + ArrayBlockingQueue p = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + boolean changed = q.retainAll(p); + if (i == 0) + assertFalse(changed); + else + assertTrue(changed); + + assertTrue(q.containsAll(p)); + assertEquals(SIZE - i, q.size()); + p.remove(); + } + } + + /** + * removeAll(c) removes only those elements of c and reports true if changed + */ + public void testRemoveAll() { + for (int i = 1; i < SIZE; ++i) { + ArrayBlockingQueue q = populatedQueue(SIZE); + ArrayBlockingQueue p = populatedQueue(i); + assertTrue(q.removeAll(p)); + assertEquals(SIZE - i, q.size()); + for (int j = 0; j < i; ++j) { + Integer x = (Integer)(p.remove()); + assertFalse(q.contains(x)); + } + } + } + + void checkToArray(ArrayBlockingQueue q) { + int size = q.size(); + Object[] o = q.toArray(); + assertEquals(size, o.length); + Iterator it = q.iterator(); + for (int i = 0; i < size; i++) { + Integer x = (Integer) it.next(); + assertEquals((Integer)o[0] + i, (int) x); + assertSame(o[i], x); + } + } + + /** + * toArray() contains all elements in FIFO order + */ + public void testToArray() { + ArrayBlockingQueue q = new ArrayBlockingQueue(SIZE); + for (int i = 0; i < SIZE; i++) { + checkToArray(q); + q.add(i); + } + // Provoke wraparound + for (int i = 0; i < SIZE; i++) { + checkToArray(q); + assertEquals(i, q.poll()); + checkToArray(q); + q.add(SIZE + i); + } + for (int i = 0; i < SIZE; i++) { + checkToArray(q); + assertEquals(SIZE + i, q.poll()); + } + } + + void checkToArray2(ArrayBlockingQueue q) { + int size = q.size(); + Integer[] a1 = (size == 0) ? null : new Integer[size - 1]; + Integer[] a2 = new Integer[size]; + Integer[] a3 = new Integer[size + 2]; + if (size > 0) Arrays.fill(a1, 42); + Arrays.fill(a2, 42); + Arrays.fill(a3, 42); + Integer[] b1 = (size == 0) ? null : (Integer[]) q.toArray(a1); + Integer[] b2 = (Integer[]) q.toArray(a2); + Integer[] b3 = (Integer[]) q.toArray(a3); + assertSame(a2, b2); + assertSame(a3, b3); + Iterator it = q.iterator(); + for (int i = 0; i < size; i++) { + Integer x = (Integer) it.next(); + assertSame(b1[i], x); + assertEquals(b1[0] + i, (int) x); + assertSame(b2[i], x); + assertSame(b3[i], x); + } + assertNull(a3[size]); + assertEquals(42, (int) a3[size + 1]); + if (size > 0) { + assertNotSame(a1, b1); + assertEquals(size, b1.length); + for (int i = 0; i < a1.length; i++) { + assertEquals(42, (int) a1[i]); + } + } + } + + /** + * toArray(a) contains all elements in FIFO order + */ + public void testToArray2() { + ArrayBlockingQueue q = new ArrayBlockingQueue(SIZE); + for (int i = 0; i < SIZE; i++) { + checkToArray2(q); + q.add(i); + } + // Provoke wraparound + for (int i = 0; i < SIZE; i++) { + checkToArray2(q); + assertEquals(i, q.poll()); + checkToArray2(q); + q.add(SIZE + i); + } + for (int i = 0; i < SIZE; i++) { + checkToArray2(q); + assertEquals(SIZE + i, q.poll()); + } + } + + /** + * toArray(incompatible array type) throws ArrayStoreException + */ + public void testToArray1_BadArg() { + ArrayBlockingQueue q = populatedQueue(SIZE); + try { + q.toArray(new String[10]); + shouldThrow(); + } catch (ArrayStoreException success) {} + } + + /** + * iterator iterates through all elements + */ + public void testIterator() throws InterruptedException { + ArrayBlockingQueue q = populatedQueue(SIZE); + Iterator it = q.iterator(); + int i; + for (i = 0; it.hasNext(); i++) + assertTrue(q.contains(it.next())); + assertEquals(i, SIZE); + assertIteratorExhausted(it); + + it = q.iterator(); + for (i = 0; it.hasNext(); i++) + assertEquals(it.next(), q.take()); + assertEquals(i, SIZE); + assertIteratorExhausted(it); + } + + /** + * iterator of empty collection has no elements + */ + public void testEmptyIterator() { + assertIteratorExhausted(new ArrayBlockingQueue(SIZE).iterator()); + } + + /** + * iterator.remove removes current element + */ + public void testIteratorRemove() { + final ArrayBlockingQueue q = new ArrayBlockingQueue(3); + q.add(two); + q.add(one); + q.add(three); + + Iterator it = q.iterator(); + it.next(); + it.remove(); + + it = q.iterator(); + assertSame(it.next(), one); + assertSame(it.next(), three); + assertFalse(it.hasNext()); + } + + /** + * iterator ordering is FIFO + */ + public void testIteratorOrdering() { + final ArrayBlockingQueue q = new ArrayBlockingQueue(3); + q.add(one); + q.add(two); + q.add(three); + + assertEquals("queue should be full", 0, q.remainingCapacity()); + + int k = 0; + for (Iterator it = q.iterator(); it.hasNext();) { + assertEquals(++k, it.next()); + } + assertEquals(3, k); + } + + /** + * Modifications do not cause iterators to fail + */ + public void testWeaklyConsistentIteration() { + final ArrayBlockingQueue q = new ArrayBlockingQueue(3); + q.add(one); + q.add(two); + q.add(three); + for (Iterator it = q.iterator(); it.hasNext();) { + q.remove(); + it.next(); + } + assertEquals(0, q.size()); + } + + /** + * toString contains toStrings of elements + */ + public void testToString() { + ArrayBlockingQueue q = populatedQueue(SIZE); + String s = q.toString(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(s.contains(String.valueOf(i))); + } + } + + /** + * offer transfers elements across Executor tasks + */ + public void testOfferInExecutor() { + final ArrayBlockingQueue q = new ArrayBlockingQueue(2); + q.add(one); + q.add(two); + final CheckedBarrier threadsStarted = new CheckedBarrier(2); + final ExecutorService executor = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(executor)) { + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(q.offer(three)); + threadsStarted.await(); + assertTrue(q.offer(three, LONG_DELAY_MS, MILLISECONDS)); + assertEquals(0, q.remainingCapacity()); + }}); + + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + assertEquals(0, q.remainingCapacity()); + assertSame(one, q.take()); + }}); + } + } + + /** + * timed poll retrieves elements across Executor threads + */ + public void testPollInExecutor() { + final ArrayBlockingQueue q = new ArrayBlockingQueue(2); + final CheckedBarrier threadsStarted = new CheckedBarrier(2); + final ExecutorService executor = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(executor)) { + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertNull(q.poll()); + threadsStarted.await(); + assertSame(one, q.poll(LONG_DELAY_MS, MILLISECONDS)); + checkEmpty(q); + }}); + + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + q.put(one); + }}); + } + } + + /** + * A deserialized serialized queue has same elements in same order + */ + public void testSerialization() throws Exception { + Queue x = populatedQueue(SIZE); + Queue y = serialClone(x); + + assertNotSame(x, y); + assertEquals(x.size(), y.size()); + assertEquals(x.toString(), y.toString()); + assertTrue(Arrays.equals(x.toArray(), y.toArray())); + while (!x.isEmpty()) { + assertFalse(y.isEmpty()); + assertEquals(x.remove(), y.remove()); + } + assertTrue(y.isEmpty()); + } + + /** + * drainTo(c) empties queue into another collection c + */ + public void testDrainTo() { + ArrayBlockingQueue q = populatedQueue(SIZE); + ArrayList l = new ArrayList(); + q.drainTo(l); + assertEquals(0, q.size()); + assertEquals(SIZE, l.size()); + for (int i = 0; i < SIZE; ++i) + assertEquals(l.get(i), new Integer(i)); + q.add(zero); + q.add(one); + assertFalse(q.isEmpty()); + assertTrue(q.contains(zero)); + assertTrue(q.contains(one)); + l.clear(); + q.drainTo(l); + assertEquals(0, q.size()); + assertEquals(2, l.size()); + for (int i = 0; i < 2; ++i) + assertEquals(l.get(i), new Integer(i)); + } + + /** + * drainTo empties full queue, unblocking a waiting put. + */ + public void testDrainToWithActivePut() throws InterruptedException { + final ArrayBlockingQueue q = populatedQueue(SIZE); + Thread t = new Thread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + q.put(new Integer(SIZE + 1)); + }}); + + t.start(); + ArrayList l = new ArrayList(); + q.drainTo(l); + assertTrue(l.size() >= SIZE); + for (int i = 0; i < SIZE; ++i) + assertEquals(l.get(i), new Integer(i)); + t.join(); + assertTrue(q.size() + l.size() >= SIZE); + } + + /** + * drainTo(c, n) empties first min(n, size) elements of queue into c + */ + public void testDrainToN() { + ArrayBlockingQueue q = new ArrayBlockingQueue(SIZE * 2); + for (int i = 0; i < SIZE + 2; ++i) { + for (int j = 0; j < SIZE; j++) + assertTrue(q.offer(new Integer(j))); + ArrayList l = new ArrayList(); + q.drainTo(l, i); + int k = (i < SIZE) ? i : SIZE; + assertEquals(k, l.size()); + assertEquals(SIZE - k, q.size()); + for (int j = 0; j < k; ++j) + assertEquals(l.get(j), new Integer(j)); + do {} while (q.poll() != null); + } + } + + /** + * remove(null), contains(null) always return false + */ + public void testNeverContainsNull() { + Collection[] qs = { + new ArrayBlockingQueue(10), + populatedQueue(2), + }; + + for (Collection q : qs) { + assertFalse(q.contains(null)); + assertFalse(q.remove(null)); + } + } +} diff --git a/jdk/test/java/util/concurrent/tck/ArrayDequeTest.java b/jdk/test/java/util/concurrent/tck/ArrayDequeTest.java new file mode 100644 index 00000000000..4241f59ef3d --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ArrayDequeTest.java @@ -0,0 +1,945 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Collection; +import java.util.Deque; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.Random; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ArrayDequeTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + + public static Test suite() { + return new TestSuite(ArrayDequeTest.class); + } + + /** + * Returns a new deque of given size containing consecutive + * Integers 0 ... n. + */ + private ArrayDeque populatedDeque(int n) { + ArrayDeque q = new ArrayDeque(); + assertTrue(q.isEmpty()); + for (int i = 0; i < n; ++i) + assertTrue(q.offerLast(new Integer(i))); + assertFalse(q.isEmpty()); + assertEquals(n, q.size()); + return q; + } + + /** + * new deque is empty + */ + public void testConstructor1() { + assertEquals(0, new ArrayDeque().size()); + } + + /** + * Initializing from null Collection throws NPE + */ + public void testConstructor3() { + try { + new ArrayDeque((Collection)null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing from Collection of null elements throws NPE + */ + public void testConstructor4() { + try { + new ArrayDeque(Arrays.asList(new Integer[SIZE])); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing from Collection with some null elements throws NPE + */ + public void testConstructor5() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); + try { + new ArrayDeque(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Deque contains all elements of collection used to initialize + */ + public void testConstructor6() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(i); + ArrayDeque q = new ArrayDeque(Arrays.asList(ints)); + for (int i = 0; i < SIZE; ++i) + assertEquals(ints[i], q.pollFirst()); + } + + /** + * isEmpty is true before add, false after + */ + public void testEmpty() { + ArrayDeque q = new ArrayDeque(); + assertTrue(q.isEmpty()); + q.add(new Integer(1)); + assertFalse(q.isEmpty()); + q.add(new Integer(2)); + q.removeFirst(); + q.removeFirst(); + assertTrue(q.isEmpty()); + } + + /** + * size changes when elements added and removed + */ + public void testSize() { + ArrayDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(SIZE - i, q.size()); + q.removeFirst(); + } + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.size()); + q.add(new Integer(i)); + } + } + + /** + * push(null) throws NPE + */ + public void testPushNull() { + ArrayDeque q = new ArrayDeque(1); + try { + q.push(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * peekFirst() returns element inserted with push + */ + public void testPush() { + ArrayDeque q = populatedDeque(3); + q.pollLast(); + q.push(four); + assertSame(four, q.peekFirst()); + } + + /** + * pop() removes next element, or throws NSEE if empty + */ + public void testPop() { + ArrayDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.pop()); + } + try { + q.pop(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * offer(null) throws NPE + */ + public void testOfferNull() { + ArrayDeque q = new ArrayDeque(); + try { + q.offer(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * offerFirst(null) throws NPE + */ + public void testOfferFirstNull() { + ArrayDeque q = new ArrayDeque(); + try { + q.offerFirst(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * offerLast(null) throws NPE + */ + public void testOfferLastNull() { + ArrayDeque q = new ArrayDeque(); + try { + q.offerLast(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * offer(x) succeeds + */ + public void testOffer() { + ArrayDeque q = new ArrayDeque(); + assertTrue(q.offer(zero)); + assertTrue(q.offer(one)); + assertSame(zero, q.peekFirst()); + assertSame(one, q.peekLast()); + } + + /** + * offerFirst(x) succeeds + */ + public void testOfferFirst() { + ArrayDeque q = new ArrayDeque(); + assertTrue(q.offerFirst(zero)); + assertTrue(q.offerFirst(one)); + assertSame(one, q.peekFirst()); + assertSame(zero, q.peekLast()); + } + + /** + * offerLast(x) succeeds + */ + public void testOfferLast() { + ArrayDeque q = new ArrayDeque(); + assertTrue(q.offerLast(zero)); + assertTrue(q.offerLast(one)); + assertSame(zero, q.peekFirst()); + assertSame(one, q.peekLast()); + } + + /** + * add(null) throws NPE + */ + public void testAddNull() { + ArrayDeque q = new ArrayDeque(); + try { + q.add(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addFirst(null) throws NPE + */ + public void testAddFirstNull() { + ArrayDeque q = new ArrayDeque(); + try { + q.addFirst(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addLast(null) throws NPE + */ + public void testAddLastNull() { + ArrayDeque q = new ArrayDeque(); + try { + q.addLast(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * add(x) succeeds + */ + public void testAdd() { + ArrayDeque q = new ArrayDeque(); + assertTrue(q.add(zero)); + assertTrue(q.add(one)); + assertSame(zero, q.peekFirst()); + assertSame(one, q.peekLast()); + } + + /** + * addFirst(x) succeeds + */ + public void testAddFirst() { + ArrayDeque q = new ArrayDeque(); + q.addFirst(zero); + q.addFirst(one); + assertSame(one, q.peekFirst()); + assertSame(zero, q.peekLast()); + } + + /** + * addLast(x) succeeds + */ + public void testAddLast() { + ArrayDeque q = new ArrayDeque(); + q.addLast(zero); + q.addLast(one); + assertSame(zero, q.peekFirst()); + assertSame(one, q.peekLast()); + } + + /** + * addAll(null) throws NPE + */ + public void testAddAll1() { + ArrayDeque q = new ArrayDeque(); + try { + q.addAll(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll of a collection with null elements throws NPE + */ + public void testAddAll2() { + ArrayDeque q = new ArrayDeque(); + try { + q.addAll(Arrays.asList(new Integer[SIZE])); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll of a collection with any null elements throws NPE after + * possibly adding some elements + */ + public void testAddAll3() { + ArrayDeque q = new ArrayDeque(); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); + try { + q.addAll(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Deque contains all elements, in traversal order, of successful addAll + */ + public void testAddAll5() { + Integer[] empty = new Integer[0]; + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(i); + ArrayDeque q = new ArrayDeque(); + assertFalse(q.addAll(Arrays.asList(empty))); + assertTrue(q.addAll(Arrays.asList(ints))); + for (int i = 0; i < SIZE; ++i) + assertEquals(ints[i], q.pollFirst()); + } + + /** + * pollFirst() succeeds unless empty + */ + public void testPollFirst() { + ArrayDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.pollFirst()); + } + assertNull(q.pollFirst()); + } + + /** + * pollLast() succeeds unless empty + */ + public void testPollLast() { + ArrayDeque q = populatedDeque(SIZE); + for (int i = SIZE - 1; i >= 0; --i) { + assertEquals(i, q.pollLast()); + } + assertNull(q.pollLast()); + } + + /** + * poll() succeeds unless empty + */ + public void testPoll() { + ArrayDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.poll()); + } + assertNull(q.poll()); + } + + /** + * remove() removes next element, or throws NSEE if empty + */ + public void testRemove() { + ArrayDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.remove()); + } + try { + q.remove(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * remove(x) removes x and returns true if present + */ + public void testRemoveElement() { + ArrayDeque q = populatedDeque(SIZE); + for (int i = 1; i < SIZE; i += 2) { + assertTrue(q.contains(i)); + assertTrue(q.remove(i)); + assertFalse(q.contains(i)); + assertTrue(q.contains(i - 1)); + } + for (int i = 0; i < SIZE; i += 2) { + assertTrue(q.contains(i)); + assertTrue(q.remove(i)); + assertFalse(q.contains(i)); + assertFalse(q.remove(i + 1)); + assertFalse(q.contains(i + 1)); + } + assertTrue(q.isEmpty()); + } + + /** + * peekFirst() returns next element, or null if empty + */ + public void testPeekFirst() { + ArrayDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.peekFirst()); + assertEquals(i, q.pollFirst()); + assertTrue(q.peekFirst() == null || + !q.peekFirst().equals(i)); + } + assertNull(q.peekFirst()); + } + + /** + * peek() returns next element, or null if empty + */ + public void testPeek() { + ArrayDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.peek()); + assertEquals(i, q.poll()); + assertTrue(q.peek() == null || + !q.peek().equals(i)); + } + assertNull(q.peek()); + } + + /** + * peekLast() returns next element, or null if empty + */ + public void testPeekLast() { + ArrayDeque q = populatedDeque(SIZE); + for (int i = SIZE - 1; i >= 0; --i) { + assertEquals(i, q.peekLast()); + assertEquals(i, q.pollLast()); + assertTrue(q.peekLast() == null || + !q.peekLast().equals(i)); + } + assertNull(q.peekLast()); + } + + /** + * element() returns first element, or throws NSEE if empty + */ + public void testElement() { + ArrayDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.element()); + assertEquals(i, q.poll()); + } + try { + q.element(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * getFirst() returns first element, or throws NSEE if empty + */ + public void testFirstElement() { + ArrayDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.getFirst()); + assertEquals(i, q.pollFirst()); + } + try { + q.getFirst(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * getLast() returns last element, or throws NSEE if empty + */ + public void testLastElement() { + ArrayDeque q = populatedDeque(SIZE); + for (int i = SIZE - 1; i >= 0; --i) { + assertEquals(i, q.getLast()); + assertEquals(i, q.pollLast()); + } + try { + q.getLast(); + shouldThrow(); + } catch (NoSuchElementException success) {} + assertNull(q.peekLast()); + } + + /** + * removeFirst() removes first element, or throws NSEE if empty + */ + public void testRemoveFirst() { + ArrayDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.removeFirst()); + } + try { + q.removeFirst(); + shouldThrow(); + } catch (NoSuchElementException success) {} + assertNull(q.peekFirst()); + } + + /** + * removeLast() removes last element, or throws NSEE if empty + */ + public void testRemoveLast() { + ArrayDeque q = populatedDeque(SIZE); + for (int i = SIZE - 1; i >= 0; --i) { + assertEquals(i, q.removeLast()); + } + try { + q.removeLast(); + shouldThrow(); + } catch (NoSuchElementException success) {} + assertNull(q.peekLast()); + } + + /** + * removeFirstOccurrence(x) removes x and returns true if present + */ + public void testRemoveFirstOccurrence() { + ArrayDeque q = populatedDeque(SIZE); + for (int i = 1; i < SIZE; i += 2) { + assertTrue(q.removeFirstOccurrence(new Integer(i))); + } + for (int i = 0; i < SIZE; i += 2) { + assertTrue(q.removeFirstOccurrence(new Integer(i))); + assertFalse(q.removeFirstOccurrence(new Integer(i + 1))); + } + assertTrue(q.isEmpty()); + } + + /** + * removeLastOccurrence(x) removes x and returns true if present + */ + public void testRemoveLastOccurrence() { + ArrayDeque q = populatedDeque(SIZE); + for (int i = 1; i < SIZE; i += 2) { + assertTrue(q.removeLastOccurrence(new Integer(i))); + } + for (int i = 0; i < SIZE; i += 2) { + assertTrue(q.removeLastOccurrence(new Integer(i))); + assertFalse(q.removeLastOccurrence(new Integer(i + 1))); + } + assertTrue(q.isEmpty()); + } + + /** + * contains(x) reports true when elements added but not yet removed + */ + public void testContains() { + ArrayDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.contains(new Integer(i))); + assertEquals(i, q.pollFirst()); + assertFalse(q.contains(new Integer(i))); + } + } + + /** + * clear removes all elements + */ + public void testClear() { + ArrayDeque q = populatedDeque(SIZE); + q.clear(); + assertTrue(q.isEmpty()); + assertEquals(0, q.size()); + assertTrue(q.add(new Integer(1))); + assertFalse(q.isEmpty()); + q.clear(); + assertTrue(q.isEmpty()); + } + + /** + * containsAll(c) is true when c contains a subset of elements + */ + public void testContainsAll() { + ArrayDeque q = populatedDeque(SIZE); + ArrayDeque p = new ArrayDeque(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.containsAll(p)); + assertFalse(p.containsAll(q)); + assertTrue(p.add(new Integer(i))); + } + assertTrue(p.containsAll(q)); + } + + /** + * retainAll(c) retains only those elements of c and reports true if changed + */ + public void testRetainAll() { + ArrayDeque q = populatedDeque(SIZE); + ArrayDeque p = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + boolean changed = q.retainAll(p); + assertEquals(changed, (i > 0)); + assertTrue(q.containsAll(p)); + assertEquals(SIZE - i, q.size()); + p.removeFirst(); + } + } + + /** + * removeAll(c) removes only those elements of c and reports true if changed + */ + public void testRemoveAll() { + for (int i = 1; i < SIZE; ++i) { + ArrayDeque q = populatedDeque(SIZE); + ArrayDeque p = populatedDeque(i); + assertTrue(q.removeAll(p)); + assertEquals(SIZE - i, q.size()); + for (int j = 0; j < i; ++j) { + assertFalse(q.contains(p.removeFirst())); + } + } + } + + void checkToArray(ArrayDeque q) { + int size = q.size(); + Object[] o = q.toArray(); + assertEquals(size, o.length); + Iterator it = q.iterator(); + for (int i = 0; i < size; i++) { + Integer x = (Integer) it.next(); + assertEquals((Integer)o[0] + i, (int) x); + assertSame(o[i], x); + } + } + + /** + * toArray() contains all elements in FIFO order + */ + public void testToArray() { + ArrayDeque q = new ArrayDeque(); + for (int i = 0; i < SIZE; i++) { + checkToArray(q); + q.addLast(i); + } + // Provoke wraparound + for (int i = 0; i < SIZE; i++) { + checkToArray(q); + assertEquals(i, q.poll()); + q.addLast(SIZE + i); + } + for (int i = 0; i < SIZE; i++) { + checkToArray(q); + assertEquals(SIZE + i, q.poll()); + } + } + + void checkToArray2(ArrayDeque q) { + int size = q.size(); + Integer[] a1 = (size == 0) ? null : new Integer[size - 1]; + Integer[] a2 = new Integer[size]; + Integer[] a3 = new Integer[size + 2]; + if (size > 0) Arrays.fill(a1, 42); + Arrays.fill(a2, 42); + Arrays.fill(a3, 42); + Integer[] b1 = (size == 0) ? null : (Integer[]) q.toArray(a1); + Integer[] b2 = (Integer[]) q.toArray(a2); + Integer[] b3 = (Integer[]) q.toArray(a3); + assertSame(a2, b2); + assertSame(a3, b3); + Iterator it = q.iterator(); + for (int i = 0; i < size; i++) { + Integer x = (Integer) it.next(); + assertSame(b1[i], x); + assertEquals(b1[0] + i, (int) x); + assertSame(b2[i], x); + assertSame(b3[i], x); + } + assertNull(a3[size]); + assertEquals(42, (int) a3[size + 1]); + if (size > 0) { + assertNotSame(a1, b1); + assertEquals(size, b1.length); + for (int i = 0; i < a1.length; i++) { + assertEquals(42, (int) a1[i]); + } + } + } + + /** + * toArray(a) contains all elements in FIFO order + */ + public void testToArray2() { + ArrayDeque q = new ArrayDeque(); + for (int i = 0; i < SIZE; i++) { + checkToArray2(q); + q.addLast(i); + } + // Provoke wraparound + for (int i = 0; i < SIZE; i++) { + checkToArray2(q); + assertEquals(i, q.poll()); + q.addLast(SIZE + i); + } + for (int i = 0; i < SIZE; i++) { + checkToArray2(q); + assertEquals(SIZE + i, q.poll()); + } + } + + /** + * toArray(null) throws NullPointerException + */ + public void testToArray_NullArg() { + ArrayDeque l = new ArrayDeque(); + l.add(new Object()); + try { + l.toArray(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * toArray(incompatible array type) throws ArrayStoreException + */ + public void testToArray1_BadArg() { + ArrayDeque l = new ArrayDeque(); + l.add(new Integer(5)); + try { + l.toArray(new String[10]); + shouldThrow(); + } catch (ArrayStoreException success) {} + } + + /** + * Iterator iterates through all elements + */ + public void testIterator() { + ArrayDeque q = populatedDeque(SIZE); + Iterator it = q.iterator(); + int i; + for (i = 0; it.hasNext(); i++) + assertTrue(q.contains(it.next())); + assertEquals(i, SIZE); + assertIteratorExhausted(it); + } + + /** + * iterator of empty collection has no elements + */ + public void testEmptyIterator() { + Deque c = new ArrayDeque(); + assertIteratorExhausted(c.iterator()); + assertIteratorExhausted(c.descendingIterator()); + } + + /** + * Iterator ordering is FIFO + */ + public void testIteratorOrdering() { + final ArrayDeque q = new ArrayDeque(); + q.add(one); + q.add(two); + q.add(three); + int k = 0; + for (Iterator it = q.iterator(); it.hasNext();) { + assertEquals(++k, it.next()); + } + + assertEquals(3, k); + } + + /** + * iterator.remove() removes current element + */ + public void testIteratorRemove() { + final ArrayDeque q = new ArrayDeque(); + final Random rng = new Random(); + for (int iters = 0; iters < 100; ++iters) { + int max = rng.nextInt(5) + 2; + int split = rng.nextInt(max - 1) + 1; + for (int j = 1; j <= max; ++j) + q.add(new Integer(j)); + Iterator it = q.iterator(); + for (int j = 1; j <= split; ++j) + assertEquals(it.next(), new Integer(j)); + it.remove(); + assertEquals(it.next(), new Integer(split + 1)); + for (int j = 1; j <= split; ++j) + q.remove(new Integer(j)); + it = q.iterator(); + for (int j = split + 1; j <= max; ++j) { + assertEquals(it.next(), new Integer(j)); + it.remove(); + } + assertFalse(it.hasNext()); + assertTrue(q.isEmpty()); + } + } + + /** + * Descending iterator iterates through all elements + */ + public void testDescendingIterator() { + ArrayDeque q = populatedDeque(SIZE); + int i = 0; + Iterator it = q.descendingIterator(); + while (it.hasNext()) { + assertTrue(q.contains(it.next())); + ++i; + } + assertEquals(i, SIZE); + assertFalse(it.hasNext()); + try { + it.next(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * Descending iterator ordering is reverse FIFO + */ + public void testDescendingIteratorOrdering() { + final ArrayDeque q = new ArrayDeque(); + for (int iters = 0; iters < 100; ++iters) { + q.add(new Integer(3)); + q.add(new Integer(2)); + q.add(new Integer(1)); + int k = 0; + for (Iterator it = q.descendingIterator(); it.hasNext();) { + assertEquals(++k, it.next()); + } + + assertEquals(3, k); + q.remove(); + q.remove(); + q.remove(); + } + } + + /** + * descendingIterator.remove() removes current element + */ + public void testDescendingIteratorRemove() { + final ArrayDeque q = new ArrayDeque(); + final Random rng = new Random(); + for (int iters = 0; iters < 100; ++iters) { + int max = rng.nextInt(5) + 2; + int split = rng.nextInt(max - 1) + 1; + for (int j = max; j >= 1; --j) + q.add(new Integer(j)); + Iterator it = q.descendingIterator(); + for (int j = 1; j <= split; ++j) + assertEquals(it.next(), new Integer(j)); + it.remove(); + assertEquals(it.next(), new Integer(split + 1)); + for (int j = 1; j <= split; ++j) + q.remove(new Integer(j)); + it = q.descendingIterator(); + for (int j = split + 1; j <= max; ++j) { + assertEquals(it.next(), new Integer(j)); + it.remove(); + } + assertFalse(it.hasNext()); + assertTrue(q.isEmpty()); + } + } + + /** + * toString() contains toStrings of elements + */ + public void testToString() { + ArrayDeque q = populatedDeque(SIZE); + String s = q.toString(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(s.contains(String.valueOf(i))); + } + } + + /** + * A deserialized serialized deque has same elements in same order + */ + public void testSerialization() throws Exception { + Queue x = populatedDeque(SIZE); + Queue y = serialClone(x); + + assertNotSame(y, x); + assertEquals(x.size(), y.size()); + assertEquals(x.toString(), y.toString()); + assertTrue(Arrays.equals(x.toArray(), y.toArray())); + while (!x.isEmpty()) { + assertFalse(y.isEmpty()); + assertEquals(x.remove(), y.remove()); + } + assertTrue(y.isEmpty()); + } + + /** + * remove(null), contains(null) always return false + */ + public void testNeverContainsNull() { + Deque[] qs = { + new ArrayDeque(), + populatedDeque(2), + }; + + for (Deque q : qs) { + assertFalse(q.contains(null)); + assertFalse(q.remove(null)); + assertFalse(q.removeFirstOccurrence(null)); + assertFalse(q.removeLastOccurrence(null)); + } + } + +} diff --git a/jdk/test/java/util/concurrent/tck/Atomic8Test.java b/jdk/test/java/util/concurrent/tck/Atomic8Test.java new file mode 100644 index 00000000000..1e49299c21a --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/Atomic8Test.java @@ -0,0 +1,596 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea and Martin Buchholz with assistance from + * members of JCP JSR-166 Expert Group and released to the public + * domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicIntegerArray; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicLongArray; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class Atomic8Test extends JSR166TestCase { + + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(Atomic8Test.class); + } + + /* + * Tests of atomic class methods accepting lambdas + * introduced in JDK8. + */ + + static long addLong17(long x) { return x + 17; } + static int addInt17(int x) { return x + 17; } + static Integer addInteger17(Integer x) { + return new Integer(x.intValue() + 17); + } + static Integer sumInteger(Integer x, Integer y) { + return new Integer(x.intValue() + y.intValue()); + } + + volatile long aLongField; + volatile int anIntField; + volatile Integer anIntegerField; + + AtomicLongFieldUpdater aLongFieldUpdater() { + return AtomicLongFieldUpdater.newUpdater + (Atomic8Test.class, "aLongField"); + } + + AtomicIntegerFieldUpdater anIntFieldUpdater() { + return AtomicIntegerFieldUpdater.newUpdater + (Atomic8Test.class, "anIntField"); + } + + AtomicReferenceFieldUpdater anIntegerFieldUpdater() { + return AtomicReferenceFieldUpdater.newUpdater + (Atomic8Test.class, Integer.class, "anIntegerField"); + } + + /** + * AtomicLong getAndUpdate returns previous value and updates + * result of supplied function + */ + public void testLongGetAndUpdate() { + AtomicLong a = new AtomicLong(1L); + assertEquals(1L, a.getAndUpdate(Atomic8Test::addLong17)); + assertEquals(18L, a.getAndUpdate(Atomic8Test::addLong17)); + assertEquals(35L, a.get()); + } + + /** + * AtomicLong updateAndGet updates with supplied function and + * returns result. + */ + public void testLongUpdateAndGet() { + AtomicLong a = new AtomicLong(1L); + assertEquals(18L, a.updateAndGet(Atomic8Test::addLong17)); + assertEquals(35L, a.updateAndGet(Atomic8Test::addLong17)); + } + + /** + * AtomicLong getAndAccumulate returns previous value and updates + * with supplied function. + */ + public void testLongGetAndAccumulate() { + AtomicLong a = new AtomicLong(1L); + assertEquals(1L, a.getAndAccumulate(2L, Long::sum)); + assertEquals(3L, a.getAndAccumulate(3L, Long::sum)); + assertEquals(6L, a.get()); + } + + /** + * AtomicLong accumulateAndGet updates with supplied function and + * returns result. + */ + public void testLongAccumulateAndGet() { + AtomicLong a = new AtomicLong(1L); + assertEquals(7L, a.accumulateAndGet(6L, Long::sum)); + assertEquals(10L, a.accumulateAndGet(3L, Long::sum)); + assertEquals(10L, a.get()); + } + + /** + * AtomicInteger getAndUpdate returns previous value and updates + * result of supplied function + */ + public void testIntGetAndUpdate() { + AtomicInteger a = new AtomicInteger(1); + assertEquals(1, a.getAndUpdate(Atomic8Test::addInt17)); + assertEquals(18, a.getAndUpdate(Atomic8Test::addInt17)); + assertEquals(35, a.get()); + } + + /** + * AtomicInteger updateAndGet updates with supplied function and + * returns result. + */ + public void testIntUpdateAndGet() { + AtomicInteger a = new AtomicInteger(1); + assertEquals(18, a.updateAndGet(Atomic8Test::addInt17)); + assertEquals(35, a.updateAndGet(Atomic8Test::addInt17)); + assertEquals(35, a.get()); + } + + /** + * AtomicInteger getAndAccumulate returns previous value and updates + * with supplied function. + */ + public void testIntGetAndAccumulate() { + AtomicInteger a = new AtomicInteger(1); + assertEquals(1, a.getAndAccumulate(2, Integer::sum)); + assertEquals(3, a.getAndAccumulate(3, Integer::sum)); + assertEquals(6, a.get()); + } + + /** + * AtomicInteger accumulateAndGet updates with supplied function and + * returns result. + */ + public void testIntAccumulateAndGet() { + AtomicInteger a = new AtomicInteger(1); + assertEquals(7, a.accumulateAndGet(6, Integer::sum)); + assertEquals(10, a.accumulateAndGet(3, Integer::sum)); + assertEquals(10, a.get()); + } + + /** + * AtomicReference getAndUpdate returns previous value and updates + * result of supplied function + */ + public void testReferenceGetAndUpdate() { + AtomicReference a = new AtomicReference(one); + assertEquals(new Integer(1), a.getAndUpdate(Atomic8Test::addInteger17)); + assertEquals(new Integer(18), a.getAndUpdate(Atomic8Test::addInteger17)); + assertEquals(new Integer(35), a.get()); + } + + /** + * AtomicReference updateAndGet updates with supplied function and + * returns result. + */ + public void testReferenceUpdateAndGet() { + AtomicReference a = new AtomicReference(one); + assertEquals(new Integer(18), a.updateAndGet(Atomic8Test::addInteger17)); + assertEquals(new Integer(35), a.updateAndGet(Atomic8Test::addInteger17)); + assertEquals(new Integer(35), a.get()); + } + + /** + * AtomicReference getAndAccumulate returns previous value and updates + * with supplied function. + */ + public void testReferenceGetAndAccumulate() { + AtomicReference a = new AtomicReference(one); + assertEquals(new Integer(1), a.getAndAccumulate(2, Atomic8Test::sumInteger)); + assertEquals(new Integer(3), a.getAndAccumulate(3, Atomic8Test::sumInteger)); + assertEquals(new Integer(6), a.get()); + } + + /** + * AtomicReference accumulateAndGet updates with supplied function and + * returns result. + */ + public void testReferenceAccumulateAndGet() { + AtomicReference a = new AtomicReference(one); + assertEquals(new Integer(7), a.accumulateAndGet(6, Atomic8Test::sumInteger)); + assertEquals(new Integer(10), a.accumulateAndGet(3, Atomic8Test::sumInteger)); + assertEquals(new Integer(10), a.get()); + } + + /** + * AtomicLongArray getAndUpdate returns previous value and updates + * result of supplied function + */ + public void testLongArrayGetAndUpdate() { + AtomicLongArray a = new AtomicLongArray(1); + a.set(0, 1); + assertEquals(1L, a.getAndUpdate(0, Atomic8Test::addLong17)); + assertEquals(18L, a.getAndUpdate(0, Atomic8Test::addLong17)); + assertEquals(35L, a.get(0)); + } + + /** + * AtomicLongArray updateAndGet updates with supplied function and + * returns result. + */ + public void testLongArrayUpdateAndGet() { + AtomicLongArray a = new AtomicLongArray(1); + a.set(0, 1); + assertEquals(18L, a.updateAndGet(0, Atomic8Test::addLong17)); + assertEquals(35L, a.updateAndGet(0, Atomic8Test::addLong17)); + assertEquals(35L, a.get(0)); + } + + /** + * AtomicLongArray getAndAccumulate returns previous value and updates + * with supplied function. + */ + public void testLongArrayGetAndAccumulate() { + AtomicLongArray a = new AtomicLongArray(1); + a.set(0, 1); + assertEquals(1L, a.getAndAccumulate(0, 2L, Long::sum)); + assertEquals(3L, a.getAndAccumulate(0, 3L, Long::sum)); + assertEquals(6L, a.get(0)); + } + + /** + * AtomicLongArray accumulateAndGet updates with supplied function and + * returns result. + */ + public void testLongArrayAccumulateAndGet() { + AtomicLongArray a = new AtomicLongArray(1); + a.set(0, 1); + assertEquals(7L, a.accumulateAndGet(0, 6L, Long::sum)); + assertEquals(10L, a.accumulateAndGet(0, 3L, Long::sum)); + assertEquals(10L, a.get(0)); + } + + /** + * AtomicIntegerArray getAndUpdate returns previous value and updates + * result of supplied function + */ + public void testIntArrayGetAndUpdate() { + AtomicIntegerArray a = new AtomicIntegerArray(1); + a.set(0, 1); + assertEquals(1, a.getAndUpdate(0, Atomic8Test::addInt17)); + assertEquals(18, a.getAndUpdate(0, Atomic8Test::addInt17)); + assertEquals(35, a.get(0)); + } + + /** + * AtomicIntegerArray updateAndGet updates with supplied function and + * returns result. + */ + public void testIntArrayUpdateAndGet() { + AtomicIntegerArray a = new AtomicIntegerArray(1); + a.set(0, 1); + assertEquals(18, a.updateAndGet(0, Atomic8Test::addInt17)); + assertEquals(35, a.updateAndGet(0, Atomic8Test::addInt17)); + assertEquals(35, a.get(0)); + } + + /** + * AtomicIntegerArray getAndAccumulate returns previous value and updates + * with supplied function. + */ + public void testIntArrayGetAndAccumulate() { + AtomicIntegerArray a = new AtomicIntegerArray(1); + a.set(0, 1); + assertEquals(1, a.getAndAccumulate(0, 2, Integer::sum)); + assertEquals(3, a.getAndAccumulate(0, 3, Integer::sum)); + assertEquals(6, a.get(0)); + } + + /** + * AtomicIntegerArray accumulateAndGet updates with supplied function and + * returns result. + */ + public void testIntArrayAccumulateAndGet() { + AtomicIntegerArray a = new AtomicIntegerArray(1); + a.set(0, 1); + assertEquals(7, a.accumulateAndGet(0, 6, Integer::sum)); + assertEquals(10, a.accumulateAndGet(0, 3, Integer::sum)); + } + + /** + * AtomicReferenceArray getAndUpdate returns previous value and updates + * result of supplied function + */ + public void testReferenceArrayGetAndUpdate() { + AtomicReferenceArray a = new AtomicReferenceArray(1); + a.set(0, one); + assertEquals(new Integer(1), a.getAndUpdate(0, Atomic8Test::addInteger17)); + assertEquals(new Integer(18), a.getAndUpdate(0, Atomic8Test::addInteger17)); + assertEquals(new Integer(35), a.get(0)); + } + + /** + * AtomicReferenceArray updateAndGet updates with supplied function and + * returns result. + */ + public void testReferenceArrayUpdateAndGet() { + AtomicReferenceArray a = new AtomicReferenceArray(1); + a.set(0, one); + assertEquals(new Integer(18), a.updateAndGet(0, Atomic8Test::addInteger17)); + assertEquals(new Integer(35), a.updateAndGet(0, Atomic8Test::addInteger17)); + } + + /** + * AtomicReferenceArray getAndAccumulate returns previous value and updates + * with supplied function. + */ + public void testReferenceArrayGetAndAccumulate() { + AtomicReferenceArray a = new AtomicReferenceArray(1); + a.set(0, one); + assertEquals(new Integer(1), a.getAndAccumulate(0, 2, Atomic8Test::sumInteger)); + assertEquals(new Integer(3), a.getAndAccumulate(0, 3, Atomic8Test::sumInteger)); + assertEquals(new Integer(6), a.get(0)); + } + + /** + * AtomicReferenceArray accumulateAndGet updates with supplied function and + * returns result. + */ + public void testReferenceArrayAccumulateAndGet() { + AtomicReferenceArray a = new AtomicReferenceArray(1); + a.set(0, one); + assertEquals(new Integer(7), a.accumulateAndGet(0, 6, Atomic8Test::sumInteger)); + assertEquals(new Integer(10), a.accumulateAndGet(0, 3, Atomic8Test::sumInteger)); + } + + /** + * AtomicLongFieldUpdater getAndUpdate returns previous value and updates + * result of supplied function + */ + public void testLongFieldUpdaterGetAndUpdate() { + AtomicLongFieldUpdater a = aLongFieldUpdater(); + a.set(this, 1); + assertEquals(1L, a.getAndUpdate(this, Atomic8Test::addLong17)); + assertEquals(18L, a.getAndUpdate(this, Atomic8Test::addLong17)); + assertEquals(35L, a.get(this)); + assertEquals(35L, aLongField); + } + + /** + * AtomicLongFieldUpdater updateAndGet updates with supplied function and + * returns result. + */ + public void testLongFieldUpdaterUpdateAndGet() { + AtomicLongFieldUpdater a = aLongFieldUpdater(); + a.set(this, 1); + assertEquals(18L, a.updateAndGet(this, Atomic8Test::addLong17)); + assertEquals(35L, a.updateAndGet(this, Atomic8Test::addLong17)); + assertEquals(35L, a.get(this)); + assertEquals(35L, aLongField); + } + + /** + * AtomicLongFieldUpdater getAndAccumulate returns previous value + * and updates with supplied function. + */ + public void testLongFieldUpdaterGetAndAccumulate() { + AtomicLongFieldUpdater a = aLongFieldUpdater(); + a.set(this, 1); + assertEquals(1L, a.getAndAccumulate(this, 2L, Long::sum)); + assertEquals(3L, a.getAndAccumulate(this, 3L, Long::sum)); + assertEquals(6L, a.get(this)); + assertEquals(6L, aLongField); + } + + /** + * AtomicLongFieldUpdater accumulateAndGet updates with supplied + * function and returns result. + */ + public void testLongFieldUpdaterAccumulateAndGet() { + AtomicLongFieldUpdater a = aLongFieldUpdater(); + a.set(this, 1); + assertEquals(7L, a.accumulateAndGet(this, 6L, Long::sum)); + assertEquals(10L, a.accumulateAndGet(this, 3L, Long::sum)); + assertEquals(10L, a.get(this)); + assertEquals(10L, aLongField); + } + + /** + * AtomicIntegerFieldUpdater getAndUpdate returns previous value and updates + * result of supplied function + */ + public void testIntegerFieldUpdaterGetAndUpdate() { + AtomicIntegerFieldUpdater a = anIntFieldUpdater(); + a.set(this, 1); + assertEquals(1, a.getAndUpdate(this, Atomic8Test::addInt17)); + assertEquals(18, a.getAndUpdate(this, Atomic8Test::addInt17)); + assertEquals(35, a.get(this)); + assertEquals(35, anIntField); + } + + /** + * AtomicIntegerFieldUpdater updateAndGet updates with supplied function and + * returns result. + */ + public void testIntegerFieldUpdaterUpdateAndGet() { + AtomicIntegerFieldUpdater a = anIntFieldUpdater(); + a.set(this, 1); + assertEquals(18, a.updateAndGet(this, Atomic8Test::addInt17)); + assertEquals(35, a.updateAndGet(this, Atomic8Test::addInt17)); + assertEquals(35, a.get(this)); + assertEquals(35, anIntField); + } + + /** + * AtomicIntegerFieldUpdater getAndAccumulate returns previous value + * and updates with supplied function. + */ + public void testIntegerFieldUpdaterGetAndAccumulate() { + AtomicIntegerFieldUpdater a = anIntFieldUpdater(); + a.set(this, 1); + assertEquals(1, a.getAndAccumulate(this, 2, Integer::sum)); + assertEquals(3, a.getAndAccumulate(this, 3, Integer::sum)); + assertEquals(6, a.get(this)); + assertEquals(6, anIntField); + } + + /** + * AtomicIntegerFieldUpdater accumulateAndGet updates with supplied + * function and returns result. + */ + public void testIntegerFieldUpdaterAccumulateAndGet() { + AtomicIntegerFieldUpdater a = anIntFieldUpdater(); + a.set(this, 1); + assertEquals(7, a.accumulateAndGet(this, 6, Integer::sum)); + assertEquals(10, a.accumulateAndGet(this, 3, Integer::sum)); + assertEquals(10, a.get(this)); + assertEquals(10, anIntField); + } + + /** + * AtomicReferenceFieldUpdater getAndUpdate returns previous value + * and updates result of supplied function + */ + public void testReferenceFieldUpdaterGetAndUpdate() { + AtomicReferenceFieldUpdater a = anIntegerFieldUpdater(); + a.set(this, one); + assertEquals(new Integer(1), a.getAndUpdate(this, Atomic8Test::addInteger17)); + assertEquals(new Integer(18), a.getAndUpdate(this, Atomic8Test::addInteger17)); + assertEquals(new Integer(35), a.get(this)); + assertEquals(new Integer(35), anIntegerField); + } + + /** + * AtomicReferenceFieldUpdater updateAndGet updates with supplied + * function and returns result. + */ + public void testReferenceFieldUpdaterUpdateAndGet() { + AtomicReferenceFieldUpdater a = anIntegerFieldUpdater(); + a.set(this, one); + assertEquals(new Integer(18), a.updateAndGet(this, Atomic8Test::addInteger17)); + assertEquals(new Integer(35), a.updateAndGet(this, Atomic8Test::addInteger17)); + assertEquals(new Integer(35), a.get(this)); + assertEquals(new Integer(35), anIntegerField); + } + + /** + * AtomicReferenceFieldUpdater returns previous value and updates + * with supplied function. + */ + public void testReferenceFieldUpdaterGetAndAccumulate() { + AtomicReferenceFieldUpdater a = anIntegerFieldUpdater(); + a.set(this, one); + assertEquals(new Integer(1), a.getAndAccumulate(this, 2, Atomic8Test::sumInteger)); + assertEquals(new Integer(3), a.getAndAccumulate(this, 3, Atomic8Test::sumInteger)); + assertEquals(new Integer(6), a.get(this)); + assertEquals(new Integer(6), anIntegerField); + } + + /** + * AtomicReferenceFieldUpdater accumulateAndGet updates with + * supplied function and returns result. + */ + public void testReferenceFieldUpdaterAccumulateAndGet() { + AtomicReferenceFieldUpdater a = anIntegerFieldUpdater(); + a.set(this, one); + assertEquals(new Integer(7), a.accumulateAndGet(this, 6, Atomic8Test::sumInteger)); + assertEquals(new Integer(10), a.accumulateAndGet(this, 3, Atomic8Test::sumInteger)); + assertEquals(new Integer(10), a.get(this)); + assertEquals(new Integer(10), anIntegerField); + } + + /** + * All Atomic getAndUpdate methods throw NullPointerException on + * null function argument + */ + public void testGetAndUpdateNPE() { + Runnable[] throwingActions = { + () -> new AtomicLong().getAndUpdate(null), + () -> new AtomicInteger().getAndUpdate(null), + () -> new AtomicReference().getAndUpdate(null), + () -> new AtomicLongArray(1).getAndUpdate(0, null), + () -> new AtomicIntegerArray(1).getAndUpdate(0, null), + () -> new AtomicReferenceArray(1).getAndUpdate(0, null), + () -> aLongFieldUpdater().getAndUpdate(this, null), + () -> anIntFieldUpdater().getAndUpdate(this, null), + () -> anIntegerFieldUpdater().getAndUpdate(this, null), + ////() -> aLongFieldUpdater().getAndUpdate(null, Atomic8Test::addLong17), + ////() -> anIntFieldUpdater().getAndUpdate(null, Atomic8Test::addInt17), + ////() -> anIntegerFieldUpdater().getAndUpdate(null, Atomic8Test::addInteger17), + }; + assertThrows(NullPointerException.class, throwingActions); + } + + /** + * All Atomic updateAndGet methods throw NullPointerException on null function argument + */ + public void testUpdateAndGetNPE() { + Runnable[] throwingActions = { + () -> new AtomicLong().updateAndGet(null), + () -> new AtomicInteger().updateAndGet(null), + () -> new AtomicReference().updateAndGet(null), + () -> new AtomicLongArray(1).updateAndGet(0, null), + () -> new AtomicIntegerArray(1).updateAndGet(0, null), + () -> new AtomicReferenceArray(1).updateAndGet(0, null), + () -> aLongFieldUpdater().updateAndGet(this, null), + () -> anIntFieldUpdater().updateAndGet(this, null), + () -> anIntegerFieldUpdater().updateAndGet(this, null), + }; + assertThrows(NullPointerException.class, throwingActions); + } + + /** + * All Atomic getAndAccumulate methods throw NullPointerException + * on null function argument + */ + public void testGetAndAccumulateNPE() { + Runnable[] throwingActions = { + () -> new AtomicLong().getAndAccumulate(1L, null), + () -> new AtomicInteger().getAndAccumulate(1, null), + () -> new AtomicReference().getAndAccumulate(one, null), + () -> new AtomicLongArray(1).getAndAccumulate(0, 1L, null), + () -> new AtomicIntegerArray(1).getAndAccumulate(0, 1, null), + () -> new AtomicReferenceArray(1).getAndAccumulate(0, one, null), + () -> aLongFieldUpdater().getAndAccumulate(this, 1L, null), + () -> anIntFieldUpdater().getAndAccumulate(this, 1, null), + () -> anIntegerFieldUpdater().getAndAccumulate(this, one, null), + }; + assertThrows(NullPointerException.class, throwingActions); + } + + /** + * All Atomic accumulateAndGet methods throw NullPointerException + * on null function argument + */ + public void testAccumulateAndGetNPE() { + Runnable[] throwingActions = { + () -> new AtomicLong().accumulateAndGet(1L, null), + () -> new AtomicInteger().accumulateAndGet(1, null), + () -> new AtomicReference().accumulateAndGet(one, null), + () -> new AtomicLongArray(1).accumulateAndGet(0, 1L, null), + () -> new AtomicIntegerArray(1).accumulateAndGet(0, 1, null), + () -> new AtomicReferenceArray(1).accumulateAndGet(0, one, null), + () -> aLongFieldUpdater().accumulateAndGet(this, 1L, null), + () -> anIntFieldUpdater().accumulateAndGet(this, 1, null), + () -> anIntegerFieldUpdater().accumulateAndGet(this, one, null), + }; + assertThrows(NullPointerException.class, throwingActions); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/AtomicBooleanTest.java b/jdk/test/java/util/concurrent/tck/AtomicBooleanTest.java new file mode 100644 index 00000000000..91b5cfa8c1f --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/AtomicBooleanTest.java @@ -0,0 +1,169 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import java.util.concurrent.atomic.AtomicBoolean; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AtomicBooleanTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(AtomicBooleanTest.class); + } + + /** + * constructor initializes to given value + */ + public void testConstructor() { + assertTrue(new AtomicBoolean(true).get()); + assertFalse(new AtomicBoolean(false).get()); + } + + /** + * default constructed initializes to false + */ + public void testConstructor2() { + AtomicBoolean ai = new AtomicBoolean(); + assertFalse(ai.get()); + } + + /** + * get returns the last value set + */ + public void testGetSet() { + AtomicBoolean ai = new AtomicBoolean(true); + assertTrue(ai.get()); + ai.set(false); + assertFalse(ai.get()); + ai.set(true); + assertTrue(ai.get()); + } + + /** + * get returns the last value lazySet in same thread + */ + public void testGetLazySet() { + AtomicBoolean ai = new AtomicBoolean(true); + assertTrue(ai.get()); + ai.lazySet(false); + assertFalse(ai.get()); + ai.lazySet(true); + assertTrue(ai.get()); + } + + /** + * compareAndSet succeeds in changing value if equal to expected else fails + */ + public void testCompareAndSet() { + AtomicBoolean ai = new AtomicBoolean(true); + assertTrue(ai.compareAndSet(true, false)); + assertFalse(ai.get()); + assertTrue(ai.compareAndSet(false, false)); + assertFalse(ai.get()); + assertFalse(ai.compareAndSet(true, false)); + assertFalse(ai.get()); + assertTrue(ai.compareAndSet(false, true)); + assertTrue(ai.get()); + } + + /** + * compareAndSet in one thread enables another waiting for value + * to succeed + */ + public void testCompareAndSetInMultipleThreads() throws Exception { + final AtomicBoolean ai = new AtomicBoolean(true); + Thread t = new Thread(new CheckedRunnable() { + public void realRun() { + while (!ai.compareAndSet(false, true)) Thread.yield(); + }}); + + t.start(); + assertTrue(ai.compareAndSet(true, false)); + t.join(LONG_DELAY_MS); + assertFalse(t.isAlive()); + } + + /** + * repeated weakCompareAndSet succeeds in changing value when equal + * to expected + */ + public void testWeakCompareAndSet() { + AtomicBoolean ai = new AtomicBoolean(true); + do {} while (!ai.weakCompareAndSet(true, false)); + assertFalse(ai.get()); + do {} while (!ai.weakCompareAndSet(false, false)); + assertFalse(ai.get()); + do {} while (!ai.weakCompareAndSet(false, true)); + assertTrue(ai.get()); + } + + /** + * getAndSet returns previous value and sets to given value + */ + public void testGetAndSet() { + AtomicBoolean ai = new AtomicBoolean(true); + assertEquals(true, ai.getAndSet(false)); + assertEquals(false, ai.getAndSet(false)); + assertEquals(false, ai.getAndSet(true)); + assertTrue(ai.get()); + } + + /** + * a deserialized serialized atomic holds same value + */ + public void testSerialization() throws Exception { + AtomicBoolean x = new AtomicBoolean(); + AtomicBoolean y = serialClone(x); + x.set(true); + AtomicBoolean z = serialClone(x); + assertTrue(x.get()); + assertFalse(y.get()); + assertTrue(z.get()); + } + + /** + * toString returns current value. + */ + public void testToString() { + AtomicBoolean ai = new AtomicBoolean(); + assertEquals(Boolean.toString(false), ai.toString()); + ai.set(true); + assertEquals(Boolean.toString(true), ai.toString()); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/AtomicIntegerArrayTest.java b/jdk/test/java/util/concurrent/tck/AtomicIntegerArrayTest.java new file mode 100644 index 00000000000..d3d8f14f5c0 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/AtomicIntegerArrayTest.java @@ -0,0 +1,370 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicIntegerArray; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AtomicIntegerArrayTest extends JSR166TestCase { + + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(AtomicIntegerArrayTest.class); + } + + /** + * constructor creates array of given size with all elements zero + */ + public void testConstructor() { + AtomicIntegerArray aa = new AtomicIntegerArray(SIZE); + for (int i = 0; i < SIZE; i++) + assertEquals(0, aa.get(i)); + } + + /** + * constructor with null array throws NPE + */ + public void testConstructor2NPE() { + try { + int[] a = null; + new AtomicIntegerArray(a); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * constructor with array is of same size and has all elements + */ + public void testConstructor2() { + int[] a = { 17, 3, -42, 99, -7 }; + AtomicIntegerArray aa = new AtomicIntegerArray(a); + assertEquals(a.length, aa.length()); + for (int i = 0; i < a.length; i++) + assertEquals(a[i], aa.get(i)); + } + + /** + * get and set for out of bound indices throw IndexOutOfBoundsException + */ + public void testIndexing() { + AtomicIntegerArray aa = new AtomicIntegerArray(SIZE); + for (int index : new int[] { -1, SIZE }) { + try { + aa.get(index); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + try { + aa.set(index, 1); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + try { + aa.lazySet(index, 1); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + try { + aa.compareAndSet(index, 1, 2); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + try { + aa.weakCompareAndSet(index, 1, 2); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + try { + aa.getAndAdd(index, 1); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + try { + aa.addAndGet(index, 1); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + } + } + + /** + * get returns the last value set at index + */ + public void testGetSet() { + AtomicIntegerArray aa = new AtomicIntegerArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.set(i, 1); + assertEquals(1, aa.get(i)); + aa.set(i, 2); + assertEquals(2, aa.get(i)); + aa.set(i, -3); + assertEquals(-3, aa.get(i)); + } + } + + /** + * get returns the last value lazySet at index by same thread + */ + public void testGetLazySet() { + AtomicIntegerArray aa = new AtomicIntegerArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.lazySet(i, 1); + assertEquals(1, aa.get(i)); + aa.lazySet(i, 2); + assertEquals(2, aa.get(i)); + aa.lazySet(i, -3); + assertEquals(-3, aa.get(i)); + } + } + + /** + * compareAndSet succeeds in changing value if equal to expected else fails + */ + public void testCompareAndSet() { + AtomicIntegerArray aa = new AtomicIntegerArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.set(i, 1); + assertTrue(aa.compareAndSet(i, 1, 2)); + assertTrue(aa.compareAndSet(i, 2, -4)); + assertEquals(-4, aa.get(i)); + assertFalse(aa.compareAndSet(i, -5, 7)); + assertEquals(-4, aa.get(i)); + assertTrue(aa.compareAndSet(i, -4, 7)); + assertEquals(7, aa.get(i)); + } + } + + /** + * compareAndSet in one thread enables another waiting for value + * to succeed + */ + public void testCompareAndSetInMultipleThreads() throws Exception { + final AtomicIntegerArray a = new AtomicIntegerArray(1); + a.set(0, 1); + Thread t = new Thread(new CheckedRunnable() { + public void realRun() { + while (!a.compareAndSet(0, 2, 3)) + Thread.yield(); + }}); + + t.start(); + assertTrue(a.compareAndSet(0, 1, 2)); + t.join(LONG_DELAY_MS); + assertFalse(t.isAlive()); + assertEquals(3, a.get(0)); + } + + /** + * repeated weakCompareAndSet succeeds in changing value when equal + * to expected + */ + public void testWeakCompareAndSet() { + AtomicIntegerArray aa = new AtomicIntegerArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.set(i, 1); + do {} while (!aa.weakCompareAndSet(i, 1, 2)); + do {} while (!aa.weakCompareAndSet(i, 2, -4)); + assertEquals(-4, aa.get(i)); + do {} while (!aa.weakCompareAndSet(i, -4, 7)); + assertEquals(7, aa.get(i)); + } + } + + /** + * getAndSet returns previous value and sets to given value at given index + */ + public void testGetAndSet() { + AtomicIntegerArray aa = new AtomicIntegerArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.set(i, 1); + assertEquals(1, aa.getAndSet(i, 0)); + assertEquals(0, aa.getAndSet(i, -10)); + assertEquals(-10, aa.getAndSet(i, 1)); + } + } + + /** + * getAndAdd returns previous value and adds given value + */ + public void testGetAndAdd() { + AtomicIntegerArray aa = new AtomicIntegerArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.set(i, 1); + assertEquals(1, aa.getAndAdd(i, 2)); + assertEquals(3, aa.get(i)); + assertEquals(3, aa.getAndAdd(i, -4)); + assertEquals(-1, aa.get(i)); + } + } + + /** + * getAndDecrement returns previous value and decrements + */ + public void testGetAndDecrement() { + AtomicIntegerArray aa = new AtomicIntegerArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.set(i, 1); + assertEquals(1, aa.getAndDecrement(i)); + assertEquals(0, aa.getAndDecrement(i)); + assertEquals(-1, aa.getAndDecrement(i)); + } + } + + /** + * getAndIncrement returns previous value and increments + */ + public void testGetAndIncrement() { + AtomicIntegerArray aa = new AtomicIntegerArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.set(i, 1); + assertEquals(1, aa.getAndIncrement(i)); + assertEquals(2, aa.get(i)); + aa.set(i, -2); + assertEquals(-2, aa.getAndIncrement(i)); + assertEquals(-1, aa.getAndIncrement(i)); + assertEquals(0, aa.getAndIncrement(i)); + assertEquals(1, aa.get(i)); + } + } + + /** + * addAndGet adds given value to current, and returns current value + */ + public void testAddAndGet() { + AtomicIntegerArray aa = new AtomicIntegerArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.set(i, 1); + assertEquals(3, aa.addAndGet(i, 2)); + assertEquals(3, aa.get(i)); + assertEquals(-1, aa.addAndGet(i, -4)); + assertEquals(-1, aa.get(i)); + } + } + + /** + * decrementAndGet decrements and returns current value + */ + public void testDecrementAndGet() { + AtomicIntegerArray aa = new AtomicIntegerArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.set(i, 1); + assertEquals(0, aa.decrementAndGet(i)); + assertEquals(-1, aa.decrementAndGet(i)); + assertEquals(-2, aa.decrementAndGet(i)); + assertEquals(-2, aa.get(i)); + } + } + + /** + * incrementAndGet increments and returns current value + */ + public void testIncrementAndGet() { + AtomicIntegerArray aa = new AtomicIntegerArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.set(i, 1); + assertEquals(2, aa.incrementAndGet(i)); + assertEquals(2, aa.get(i)); + aa.set(i, -2); + assertEquals(-1, aa.incrementAndGet(i)); + assertEquals(0, aa.incrementAndGet(i)); + assertEquals(1, aa.incrementAndGet(i)); + assertEquals(1, aa.get(i)); + } + } + + class Counter extends CheckedRunnable { + final AtomicIntegerArray aa; + volatile int counts; + Counter(AtomicIntegerArray a) { aa = a; } + public void realRun() { + for (;;) { + boolean done = true; + for (int i = 0; i < aa.length(); i++) { + int v = aa.get(i); + assertTrue(v >= 0); + if (v != 0) { + done = false; + if (aa.compareAndSet(i, v, v - 1)) + ++counts; + } + } + if (done) + break; + } + } + } + + /** + * Multiple threads using same array of counters successfully + * update a number of times equal to total count + */ + public void testCountingInMultipleThreads() throws InterruptedException { + final AtomicIntegerArray aa = new AtomicIntegerArray(SIZE); + int countdown = 10000; + for (int i = 0; i < SIZE; i++) + aa.set(i, countdown); + Counter c1 = new Counter(aa); + Counter c2 = new Counter(aa); + Thread t1 = new Thread(c1); + Thread t2 = new Thread(c2); + t1.start(); + t2.start(); + t1.join(); + t2.join(); + assertEquals(c1.counts+c2.counts, SIZE * countdown); + } + + /** + * a deserialized serialized array holds same values + */ + public void testSerialization() throws Exception { + AtomicIntegerArray x = new AtomicIntegerArray(SIZE); + for (int i = 0; i < SIZE; i++) + x.set(i, -i); + AtomicIntegerArray y = serialClone(x); + assertNotSame(x, y); + assertEquals(x.length(), y.length()); + for (int i = 0; i < SIZE; i++) { + assertEquals(x.get(i), y.get(i)); + } + } + + /** + * toString returns current value. + */ + public void testToString() { + int[] a = { 17, 3, -42, 99, -7 }; + AtomicIntegerArray aa = new AtomicIntegerArray(a); + assertEquals(Arrays.toString(a), aa.toString()); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/AtomicIntegerFieldUpdaterTest.java b/jdk/test/java/util/concurrent/tck/AtomicIntegerFieldUpdaterTest.java new file mode 100644 index 00000000000..5f7612de975 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/AtomicIntegerFieldUpdaterTest.java @@ -0,0 +1,363 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AtomicIntegerFieldUpdaterTest extends JSR166TestCase { + volatile int x = 0; + protected volatile int protectedField; + private volatile int privateField; + int w; + float z; + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(AtomicIntegerFieldUpdaterTest.class); + } + + // for testing subclass access + static class AtomicIntegerFieldUpdaterTestSubclass extends AtomicIntegerFieldUpdaterTest { + public void checkPrivateAccess() { + try { + AtomicIntegerFieldUpdater a = + AtomicIntegerFieldUpdater.newUpdater + (AtomicIntegerFieldUpdaterTest.class, "privateField"); + shouldThrow(); + } catch (RuntimeException success) { + assertNotNull(success.getCause()); + } + } + + public void checkCompareAndSetProtectedSub() { + AtomicIntegerFieldUpdater a = + AtomicIntegerFieldUpdater.newUpdater + (AtomicIntegerFieldUpdaterTest.class, "protectedField"); + this.protectedField = 1; + assertTrue(a.compareAndSet(this, 1, 2)); + assertTrue(a.compareAndSet(this, 2, -4)); + assertEquals(-4, a.get(this)); + assertFalse(a.compareAndSet(this, -5, 7)); + assertEquals(-4, a.get(this)); + assertTrue(a.compareAndSet(this, -4, 7)); + assertEquals(7, a.get(this)); + } + } + + static class UnrelatedClass { + public void checkPackageAccess(AtomicIntegerFieldUpdaterTest obj) { + obj.x = 72; + AtomicIntegerFieldUpdater a = + AtomicIntegerFieldUpdater.newUpdater + (AtomicIntegerFieldUpdaterTest.class, "x"); + assertEquals(72, a.get(obj)); + assertTrue(a.compareAndSet(obj, 72, 73)); + assertEquals(73, a.get(obj)); + } + + public void checkPrivateAccess(AtomicIntegerFieldUpdaterTest obj) { + try { + AtomicIntegerFieldUpdater a = + AtomicIntegerFieldUpdater.newUpdater + (AtomicIntegerFieldUpdaterTest.class, "privateField"); + throw new AssertionError("should throw"); + } catch (RuntimeException success) { + assertNotNull(success.getCause()); + } + } + } + + AtomicIntegerFieldUpdater updaterFor(String fieldName) { + return AtomicIntegerFieldUpdater.newUpdater + (AtomicIntegerFieldUpdaterTest.class, fieldName); + } + + /** + * Construction with non-existent field throws RuntimeException + */ + public void testConstructor() { + try { + updaterFor("y"); + shouldThrow(); + } catch (RuntimeException success) { + assertNotNull(success.getCause()); + } + } + + /** + * construction with field not of given type throws IllegalArgumentException + */ + public void testConstructor2() { + try { + updaterFor("z"); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * construction with non-volatile field throws IllegalArgumentException + */ + public void testConstructor3() { + try { + updaterFor("w"); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * construction using private field from subclass throws RuntimeException + */ + public void testPrivateFieldInSubclass() { + AtomicIntegerFieldUpdaterTestSubclass s = + new AtomicIntegerFieldUpdaterTestSubclass(); + s.checkPrivateAccess(); + } + + /** + * construction from unrelated class; package access is allowed, + * private access is not + */ + public void testUnrelatedClassAccess() { + new UnrelatedClass().checkPackageAccess(this); + new UnrelatedClass().checkPrivateAccess(this); + } + + /** + * get returns the last value set or assigned + */ + public void testGetSet() { + AtomicIntegerFieldUpdater a; + a = updaterFor("x"); + x = 1; + assertEquals(1, a.get(this)); + a.set(this, 2); + assertEquals(2, a.get(this)); + a.set(this, -3); + assertEquals(-3, a.get(this)); + } + + /** + * get returns the last value lazySet by same thread + */ + public void testGetLazySet() { + AtomicIntegerFieldUpdater a; + a = updaterFor("x"); + x = 1; + assertEquals(1, a.get(this)); + a.lazySet(this, 2); + assertEquals(2, a.get(this)); + a.lazySet(this, -3); + assertEquals(-3, a.get(this)); + } + + /** + * compareAndSet succeeds in changing value if equal to expected else fails + */ + public void testCompareAndSet() { + AtomicIntegerFieldUpdater a; + a = updaterFor("x"); + x = 1; + assertTrue(a.compareAndSet(this, 1, 2)); + assertTrue(a.compareAndSet(this, 2, -4)); + assertEquals(-4, a.get(this)); + assertFalse(a.compareAndSet(this, -5, 7)); + assertEquals(-4, a.get(this)); + assertTrue(a.compareAndSet(this, -4, 7)); + assertEquals(7, a.get(this)); + } + + /** + * compareAndSet succeeds in changing protected field value if + * equal to expected else fails + */ + public void testCompareAndSetProtected() { + AtomicIntegerFieldUpdater a; + a = updaterFor("protectedField"); + protectedField = 1; + assertTrue(a.compareAndSet(this, 1, 2)); + assertTrue(a.compareAndSet(this, 2, -4)); + assertEquals(-4, a.get(this)); + assertFalse(a.compareAndSet(this, -5, 7)); + assertEquals(-4, a.get(this)); + assertTrue(a.compareAndSet(this, -4, 7)); + assertEquals(7, a.get(this)); + } + + /** + * compareAndSet succeeds in changing protected field value if + * equal to expected else fails + */ + public void testCompareAndSetProtectedInSubclass() { + AtomicIntegerFieldUpdaterTestSubclass s = + new AtomicIntegerFieldUpdaterTestSubclass(); + s.checkCompareAndSetProtectedSub(); + } + + /** + * compareAndSet in one thread enables another waiting for value + * to succeed + */ + public void testCompareAndSetInMultipleThreads() throws Exception { + x = 1; + final AtomicIntegerFieldUpdater a; + a = updaterFor("x"); + + Thread t = new Thread(new CheckedRunnable() { + public void realRun() { + while (!a.compareAndSet(AtomicIntegerFieldUpdaterTest.this, 2, 3)) + Thread.yield(); + }}); + + t.start(); + assertTrue(a.compareAndSet(this, 1, 2)); + t.join(LONG_DELAY_MS); + assertFalse(t.isAlive()); + assertEquals(3, a.get(this)); + } + + /** + * repeated weakCompareAndSet succeeds in changing value when equal + * to expected + */ + public void testWeakCompareAndSet() { + AtomicIntegerFieldUpdater a; + a = updaterFor("x"); + x = 1; + do {} while (!a.weakCompareAndSet(this, 1, 2)); + do {} while (!a.weakCompareAndSet(this, 2, -4)); + assertEquals(-4, a.get(this)); + do {} while (!a.weakCompareAndSet(this, -4, 7)); + assertEquals(7, a.get(this)); + } + + /** + * getAndSet returns previous value and sets to given value + */ + public void testGetAndSet() { + AtomicIntegerFieldUpdater a; + a = updaterFor("x"); + x = 1; + assertEquals(1, a.getAndSet(this, 0)); + assertEquals(0, a.getAndSet(this, -10)); + assertEquals(-10, a.getAndSet(this, 1)); + } + + /** + * getAndAdd returns previous value and adds given value + */ + public void testGetAndAdd() { + AtomicIntegerFieldUpdater a; + a = updaterFor("x"); + x = 1; + assertEquals(1, a.getAndAdd(this, 2)); + assertEquals(3, a.get(this)); + assertEquals(3, a.getAndAdd(this, -4)); + assertEquals(-1, a.get(this)); + } + + /** + * getAndDecrement returns previous value and decrements + */ + public void testGetAndDecrement() { + AtomicIntegerFieldUpdater a; + a = updaterFor("x"); + x = 1; + assertEquals(1, a.getAndDecrement(this)); + assertEquals(0, a.getAndDecrement(this)); + assertEquals(-1, a.getAndDecrement(this)); + } + + /** + * getAndIncrement returns previous value and increments + */ + public void testGetAndIncrement() { + AtomicIntegerFieldUpdater a; + a = updaterFor("x"); + x = 1; + assertEquals(1, a.getAndIncrement(this)); + assertEquals(2, a.get(this)); + a.set(this, -2); + assertEquals(-2, a.getAndIncrement(this)); + assertEquals(-1, a.getAndIncrement(this)); + assertEquals(0, a.getAndIncrement(this)); + assertEquals(1, a.get(this)); + } + + /** + * addAndGet adds given value to current, and returns current value + */ + public void testAddAndGet() { + AtomicIntegerFieldUpdater a; + a = updaterFor("x"); + x = 1; + assertEquals(3, a.addAndGet(this, 2)); + assertEquals(3, a.get(this)); + assertEquals(-1, a.addAndGet(this, -4)); + assertEquals(-1, a.get(this)); + } + + /** + * decrementAndGet decrements and returns current value + */ + public void testDecrementAndGet() { + AtomicIntegerFieldUpdater a; + a = updaterFor("x"); + x = 1; + assertEquals(0, a.decrementAndGet(this)); + assertEquals(-1, a.decrementAndGet(this)); + assertEquals(-2, a.decrementAndGet(this)); + assertEquals(-2, a.get(this)); + } + + /** + * incrementAndGet increments and returns current value + */ + public void testIncrementAndGet() { + AtomicIntegerFieldUpdater a; + a = updaterFor("x"); + x = 1; + assertEquals(2, a.incrementAndGet(this)); + assertEquals(2, a.get(this)); + a.set(this, -2); + assertEquals(-1, a.incrementAndGet(this)); + assertEquals(0, a.incrementAndGet(this)); + assertEquals(1, a.incrementAndGet(this)); + assertEquals(1, a.get(this)); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/AtomicIntegerTest.java b/jdk/test/java/util/concurrent/tck/AtomicIntegerTest.java new file mode 100644 index 00000000000..d148788d792 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/AtomicIntegerTest.java @@ -0,0 +1,294 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import java.util.concurrent.atomic.AtomicInteger; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AtomicIntegerTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(AtomicIntegerTest.class); + } + + final int[] VALUES = { + Integer.MIN_VALUE, -1, 0, 1, 42, Integer.MAX_VALUE, + }; + + /** + * constructor initializes to given value + */ + public void testConstructor() { + AtomicInteger ai = new AtomicInteger(1); + assertEquals(1, ai.get()); + } + + /** + * default constructed initializes to zero + */ + public void testConstructor2() { + AtomicInteger ai = new AtomicInteger(); + assertEquals(0, ai.get()); + } + + /** + * get returns the last value set + */ + public void testGetSet() { + AtomicInteger ai = new AtomicInteger(1); + assertEquals(1, ai.get()); + ai.set(2); + assertEquals(2, ai.get()); + ai.set(-3); + assertEquals(-3, ai.get()); + } + + /** + * get returns the last value lazySet in same thread + */ + public void testGetLazySet() { + AtomicInteger ai = new AtomicInteger(1); + assertEquals(1, ai.get()); + ai.lazySet(2); + assertEquals(2, ai.get()); + ai.lazySet(-3); + assertEquals(-3, ai.get()); + } + + /** + * compareAndSet succeeds in changing value if equal to expected else fails + */ + public void testCompareAndSet() { + AtomicInteger ai = new AtomicInteger(1); + assertTrue(ai.compareAndSet(1, 2)); + assertTrue(ai.compareAndSet(2, -4)); + assertEquals(-4, ai.get()); + assertFalse(ai.compareAndSet(-5, 7)); + assertEquals(-4, ai.get()); + assertTrue(ai.compareAndSet(-4, 7)); + assertEquals(7, ai.get()); + } + + /** + * compareAndSet in one thread enables another waiting for value + * to succeed + */ + public void testCompareAndSetInMultipleThreads() throws Exception { + final AtomicInteger ai = new AtomicInteger(1); + Thread t = new Thread(new CheckedRunnable() { + public void realRun() { + while (!ai.compareAndSet(2, 3)) + Thread.yield(); + }}); + + t.start(); + assertTrue(ai.compareAndSet(1, 2)); + t.join(LONG_DELAY_MS); + assertFalse(t.isAlive()); + assertEquals(3, ai.get()); + } + + /** + * repeated weakCompareAndSet succeeds in changing value when equal + * to expected + */ + public void testWeakCompareAndSet() { + AtomicInteger ai = new AtomicInteger(1); + do {} while (!ai.weakCompareAndSet(1, 2)); + do {} while (!ai.weakCompareAndSet(2, -4)); + assertEquals(-4, ai.get()); + do {} while (!ai.weakCompareAndSet(-4, 7)); + assertEquals(7, ai.get()); + } + + /** + * getAndSet returns previous value and sets to given value + */ + public void testGetAndSet() { + AtomicInteger ai = new AtomicInteger(1); + assertEquals(1, ai.getAndSet(0)); + assertEquals(0, ai.getAndSet(-10)); + assertEquals(-10, ai.getAndSet(1)); + } + + /** + * getAndAdd returns previous value and adds given value + */ + public void testGetAndAdd() { + AtomicInteger ai = new AtomicInteger(1); + assertEquals(1, ai.getAndAdd(2)); + assertEquals(3, ai.get()); + assertEquals(3, ai.getAndAdd(-4)); + assertEquals(-1, ai.get()); + } + + /** + * getAndDecrement returns previous value and decrements + */ + public void testGetAndDecrement() { + AtomicInteger ai = new AtomicInteger(1); + assertEquals(1, ai.getAndDecrement()); + assertEquals(0, ai.getAndDecrement()); + assertEquals(-1, ai.getAndDecrement()); + } + + /** + * getAndIncrement returns previous value and increments + */ + public void testGetAndIncrement() { + AtomicInteger ai = new AtomicInteger(1); + assertEquals(1, ai.getAndIncrement()); + assertEquals(2, ai.get()); + ai.set(-2); + assertEquals(-2, ai.getAndIncrement()); + assertEquals(-1, ai.getAndIncrement()); + assertEquals(0, ai.getAndIncrement()); + assertEquals(1, ai.get()); + } + + /** + * addAndGet adds given value to current, and returns current value + */ + public void testAddAndGet() { + AtomicInteger ai = new AtomicInteger(1); + assertEquals(3, ai.addAndGet(2)); + assertEquals(3, ai.get()); + assertEquals(-1, ai.addAndGet(-4)); + assertEquals(-1, ai.get()); + } + + /** + * decrementAndGet decrements and returns current value + */ + public void testDecrementAndGet() { + AtomicInteger ai = new AtomicInteger(1); + assertEquals(0, ai.decrementAndGet()); + assertEquals(-1, ai.decrementAndGet()); + assertEquals(-2, ai.decrementAndGet()); + assertEquals(-2, ai.get()); + } + + /** + * incrementAndGet increments and returns current value + */ + public void testIncrementAndGet() { + AtomicInteger ai = new AtomicInteger(1); + assertEquals(2, ai.incrementAndGet()); + assertEquals(2, ai.get()); + ai.set(-2); + assertEquals(-1, ai.incrementAndGet()); + assertEquals(0, ai.incrementAndGet()); + assertEquals(1, ai.incrementAndGet()); + assertEquals(1, ai.get()); + } + + /** + * a deserialized serialized atomic holds same value + */ + public void testSerialization() throws Exception { + AtomicInteger x = new AtomicInteger(); + AtomicInteger y = serialClone(x); + assertNotSame(x, y); + x.set(22); + AtomicInteger z = serialClone(x); + assertEquals(22, x.get()); + assertEquals(0, y.get()); + assertEquals(22, z.get()); + } + + /** + * toString returns current value. + */ + public void testToString() { + AtomicInteger ai = new AtomicInteger(); + assertEquals("0", ai.toString()); + for (int x : VALUES) { + ai.set(x); + assertEquals(Integer.toString(x), ai.toString()); + } + } + + /** + * intValue returns current value. + */ + public void testIntValue() { + AtomicInteger ai = new AtomicInteger(); + assertEquals(0, ai.intValue()); + for (int x : VALUES) { + ai.set(x); + assertEquals(x, ai.intValue()); + } + } + + /** + * longValue returns current value. + */ + public void testLongValue() { + AtomicInteger ai = new AtomicInteger(); + assertEquals(0L, ai.longValue()); + for (int x : VALUES) { + ai.set(x); + assertEquals((long)x, ai.longValue()); + } + } + + /** + * floatValue returns current value. + */ + public void testFloatValue() { + AtomicInteger ai = new AtomicInteger(); + assertEquals(0.0f, ai.floatValue()); + for (int x : VALUES) { + ai.set(x); + assertEquals((float)x, ai.floatValue()); + } + } + + /** + * doubleValue returns current value. + */ + public void testDoubleValue() { + AtomicInteger ai = new AtomicInteger(); + assertEquals(0.0d, ai.doubleValue()); + for (int x : VALUES) { + ai.set(x); + assertEquals((double)x, ai.doubleValue()); + } + } + +} diff --git a/jdk/test/java/util/concurrent/tck/AtomicLongArrayTest.java b/jdk/test/java/util/concurrent/tck/AtomicLongArrayTest.java new file mode 100644 index 00000000000..bd74addbf7f --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/AtomicLongArrayTest.java @@ -0,0 +1,369 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicLongArray; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AtomicLongArrayTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(AtomicLongArrayTest.class); + } + + /** + * constructor creates array of given size with all elements zero + */ + public void testConstructor() { + AtomicLongArray aa = new AtomicLongArray(SIZE); + for (int i = 0; i < SIZE; i++) + assertEquals(0, aa.get(i)); + } + + /** + * constructor with null array throws NPE + */ + public void testConstructor2NPE() { + try { + long[] a = null; + new AtomicLongArray(a); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * constructor with array is of same size and has all elements + */ + public void testConstructor2() { + long[] a = { 17L, 3L, -42L, 99L, -7L }; + AtomicLongArray aa = new AtomicLongArray(a); + assertEquals(a.length, aa.length()); + for (int i = 0; i < a.length; i++) + assertEquals(a[i], aa.get(i)); + } + + /** + * get and set for out of bound indices throw IndexOutOfBoundsException + */ + public void testIndexing() { + AtomicLongArray aa = new AtomicLongArray(SIZE); + for (int index : new int[] { -1, SIZE }) { + try { + aa.get(index); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + try { + aa.set(index, 1); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + try { + aa.lazySet(index, 1); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + try { + aa.compareAndSet(index, 1, 2); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + try { + aa.weakCompareAndSet(index, 1, 2); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + try { + aa.getAndAdd(index, 1); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + try { + aa.addAndGet(index, 1); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + } + } + + /** + * get returns the last value set at index + */ + public void testGetSet() { + AtomicLongArray aa = new AtomicLongArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.set(i, 1); + assertEquals(1, aa.get(i)); + aa.set(i, 2); + assertEquals(2, aa.get(i)); + aa.set(i, -3); + assertEquals(-3, aa.get(i)); + } + } + + /** + * get returns the last value lazySet at index by same thread + */ + public void testGetLazySet() { + AtomicLongArray aa = new AtomicLongArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.lazySet(i, 1); + assertEquals(1, aa.get(i)); + aa.lazySet(i, 2); + assertEquals(2, aa.get(i)); + aa.lazySet(i, -3); + assertEquals(-3, aa.get(i)); + } + } + + /** + * compareAndSet succeeds in changing value if equal to expected else fails + */ + public void testCompareAndSet() { + AtomicLongArray aa = new AtomicLongArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.set(i, 1); + assertTrue(aa.compareAndSet(i, 1, 2)); + assertTrue(aa.compareAndSet(i, 2, -4)); + assertEquals(-4, aa.get(i)); + assertFalse(aa.compareAndSet(i, -5, 7)); + assertEquals(-4, aa.get(i)); + assertTrue(aa.compareAndSet(i, -4, 7)); + assertEquals(7, aa.get(i)); + } + } + + /** + * compareAndSet in one thread enables another waiting for value + * to succeed + */ + public void testCompareAndSetInMultipleThreads() throws InterruptedException { + final AtomicLongArray a = new AtomicLongArray(1); + a.set(0, 1); + Thread t = new Thread(new CheckedRunnable() { + public void realRun() { + while (!a.compareAndSet(0, 2, 3)) + Thread.yield(); + }}); + + t.start(); + assertTrue(a.compareAndSet(0, 1, 2)); + t.join(LONG_DELAY_MS); + assertFalse(t.isAlive()); + assertEquals(3, a.get(0)); + } + + /** + * repeated weakCompareAndSet succeeds in changing value when equal + * to expected + */ + public void testWeakCompareAndSet() { + AtomicLongArray aa = new AtomicLongArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.set(i, 1); + do {} while (!aa.weakCompareAndSet(i, 1, 2)); + do {} while (!aa.weakCompareAndSet(i, 2, -4)); + assertEquals(-4, aa.get(i)); + do {} while (!aa.weakCompareAndSet(i, -4, 7)); + assertEquals(7, aa.get(i)); + } + } + + /** + * getAndSet returns previous value and sets to given value at given index + */ + public void testGetAndSet() { + AtomicLongArray aa = new AtomicLongArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.set(i, 1); + assertEquals(1, aa.getAndSet(i, 0)); + assertEquals(0, aa.getAndSet(i, -10)); + assertEquals(-10, aa.getAndSet(i, 1)); + } + } + + /** + * getAndAdd returns previous value and adds given value + */ + public void testGetAndAdd() { + AtomicLongArray aa = new AtomicLongArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.set(i, 1); + assertEquals(1, aa.getAndAdd(i, 2)); + assertEquals(3, aa.get(i)); + assertEquals(3, aa.getAndAdd(i, -4)); + assertEquals(-1, aa.get(i)); + } + } + + /** + * getAndDecrement returns previous value and decrements + */ + public void testGetAndDecrement() { + AtomicLongArray aa = new AtomicLongArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.set(i, 1); + assertEquals(1, aa.getAndDecrement(i)); + assertEquals(0, aa.getAndDecrement(i)); + assertEquals(-1, aa.getAndDecrement(i)); + } + } + + /** + * getAndIncrement returns previous value and increments + */ + public void testGetAndIncrement() { + AtomicLongArray aa = new AtomicLongArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.set(i, 1); + assertEquals(1, aa.getAndIncrement(i)); + assertEquals(2, aa.get(i)); + aa.set(i, -2); + assertEquals(-2, aa.getAndIncrement(i)); + assertEquals(-1, aa.getAndIncrement(i)); + assertEquals(0, aa.getAndIncrement(i)); + assertEquals(1, aa.get(i)); + } + } + + /** + * addAndGet adds given value to current, and returns current value + */ + public void testAddAndGet() { + AtomicLongArray aa = new AtomicLongArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.set(i, 1); + assertEquals(3, aa.addAndGet(i, 2)); + assertEquals(3, aa.get(i)); + assertEquals(-1, aa.addAndGet(i, -4)); + assertEquals(-1, aa.get(i)); + } + } + + /** + * decrementAndGet decrements and returns current value + */ + public void testDecrementAndGet() { + AtomicLongArray aa = new AtomicLongArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.set(i, 1); + assertEquals(0, aa.decrementAndGet(i)); + assertEquals(-1, aa.decrementAndGet(i)); + assertEquals(-2, aa.decrementAndGet(i)); + assertEquals(-2, aa.get(i)); + } + } + + /** + * incrementAndGet increments and returns current value + */ + public void testIncrementAndGet() { + AtomicLongArray aa = new AtomicLongArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.set(i, 1); + assertEquals(2, aa.incrementAndGet(i)); + assertEquals(2, aa.get(i)); + aa.set(i, -2); + assertEquals(-1, aa.incrementAndGet(i)); + assertEquals(0, aa.incrementAndGet(i)); + assertEquals(1, aa.incrementAndGet(i)); + assertEquals(1, aa.get(i)); + } + } + + class Counter extends CheckedRunnable { + final AtomicLongArray aa; + volatile long counts; + Counter(AtomicLongArray a) { aa = a; } + public void realRun() { + for (;;) { + boolean done = true; + for (int i = 0; i < aa.length(); i++) { + long v = aa.get(i); + assertTrue(v >= 0); + if (v != 0) { + done = false; + if (aa.compareAndSet(i, v, v - 1)) + ++counts; + } + } + if (done) + break; + } + } + } + + /** + * Multiple threads using same array of counters successfully + * update a number of times equal to total count + */ + public void testCountingInMultipleThreads() throws InterruptedException { + final AtomicLongArray aa = new AtomicLongArray(SIZE); + long countdown = 10000; + for (int i = 0; i < SIZE; i++) + aa.set(i, countdown); + Counter c1 = new Counter(aa); + Counter c2 = new Counter(aa); + Thread t1 = new Thread(c1); + Thread t2 = new Thread(c2); + t1.start(); + t2.start(); + t1.join(); + t2.join(); + assertEquals(c1.counts+c2.counts, SIZE * countdown); + } + + /** + * a deserialized serialized array holds same values + */ + public void testSerialization() throws Exception { + AtomicLongArray x = new AtomicLongArray(SIZE); + for (int i = 0; i < SIZE; i++) + x.set(i, -i); + AtomicLongArray y = serialClone(x); + assertNotSame(x, y); + assertEquals(x.length(), y.length()); + for (int i = 0; i < SIZE; i++) { + assertEquals(x.get(i), y.get(i)); + } + } + + /** + * toString returns current value. + */ + public void testToString() { + long[] a = { 17, 3, -42, 99, -7 }; + AtomicLongArray aa = new AtomicLongArray(a); + assertEquals(Arrays.toString(a), aa.toString()); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/AtomicLongFieldUpdaterTest.java b/jdk/test/java/util/concurrent/tck/AtomicLongFieldUpdaterTest.java new file mode 100644 index 00000000000..b9dc1017017 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/AtomicLongFieldUpdaterTest.java @@ -0,0 +1,363 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import java.util.concurrent.atomic.AtomicLongFieldUpdater; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AtomicLongFieldUpdaterTest extends JSR166TestCase { + volatile long x = 0; + protected volatile long protectedField; + private volatile long privateField; + long w; + float z; + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(AtomicLongFieldUpdaterTest.class); + } + + // for testing subclass access + static class AtomicLongFieldUpdaterTestSubclass extends AtomicLongFieldUpdaterTest { + public void checkPrivateAccess() { + try { + AtomicLongFieldUpdater a = + AtomicLongFieldUpdater.newUpdater + (AtomicLongFieldUpdaterTest.class, "privateField"); + shouldThrow(); + } catch (RuntimeException success) { + assertNotNull(success.getCause()); + } + } + + public void checkCompareAndSetProtectedSub() { + AtomicLongFieldUpdater a = + AtomicLongFieldUpdater.newUpdater + (AtomicLongFieldUpdaterTest.class, "protectedField"); + this.protectedField = 1; + assertTrue(a.compareAndSet(this, 1, 2)); + assertTrue(a.compareAndSet(this, 2, -4)); + assertEquals(-4, a.get(this)); + assertFalse(a.compareAndSet(this, -5, 7)); + assertEquals(-4, a.get(this)); + assertTrue(a.compareAndSet(this, -4, 7)); + assertEquals(7, a.get(this)); + } + } + + static class UnrelatedClass { + public void checkPackageAccess(AtomicLongFieldUpdaterTest obj) { + obj.x = 72L; + AtomicLongFieldUpdater a = + AtomicLongFieldUpdater.newUpdater + (AtomicLongFieldUpdaterTest.class, "x"); + assertEquals(72L, a.get(obj)); + assertTrue(a.compareAndSet(obj, 72L, 73L)); + assertEquals(73L, a.get(obj)); + } + + public void checkPrivateAccess(AtomicLongFieldUpdaterTest obj) { + try { + AtomicLongFieldUpdater a = + AtomicLongFieldUpdater.newUpdater + (AtomicLongFieldUpdaterTest.class, "privateField"); + throw new AssertionError("should throw"); + } catch (RuntimeException success) { + assertNotNull(success.getCause()); + } + } + } + + AtomicLongFieldUpdater updaterFor(String fieldName) { + return AtomicLongFieldUpdater.newUpdater + (AtomicLongFieldUpdaterTest.class, fieldName); + } + + /** + * Construction with non-existent field throws RuntimeException + */ + public void testConstructor() { + try { + updaterFor("y"); + shouldThrow(); + } catch (RuntimeException success) { + assertNotNull(success.getCause()); + } + } + + /** + * construction with field not of given type throws IllegalArgumentException + */ + public void testConstructor2() { + try { + updaterFor("z"); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * construction with non-volatile field throws IllegalArgumentException + */ + public void testConstructor3() { + try { + updaterFor("w"); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * construction using private field from subclass throws RuntimeException + */ + public void testPrivateFieldInSubclass() { + AtomicLongFieldUpdaterTestSubclass s = + new AtomicLongFieldUpdaterTestSubclass(); + s.checkPrivateAccess(); + } + + /** + * construction from unrelated class; package access is allowed, + * private access is not + */ + public void testUnrelatedClassAccess() { + new UnrelatedClass().checkPackageAccess(this); + new UnrelatedClass().checkPrivateAccess(this); + } + + /** + * get returns the last value set or assigned + */ + public void testGetSet() { + AtomicLongFieldUpdater a; + a = updaterFor("x"); + x = 1; + assertEquals(1, a.get(this)); + a.set(this, 2); + assertEquals(2, a.get(this)); + a.set(this, -3); + assertEquals(-3, a.get(this)); + } + + /** + * get returns the last value lazySet by same thread + */ + public void testGetLazySet() { + AtomicLongFieldUpdater a; + a = updaterFor("x"); + x = 1; + assertEquals(1, a.get(this)); + a.lazySet(this, 2); + assertEquals(2, a.get(this)); + a.lazySet(this, -3); + assertEquals(-3, a.get(this)); + } + + /** + * compareAndSet succeeds in changing value if equal to expected else fails + */ + public void testCompareAndSet() { + AtomicLongFieldUpdater a; + a = updaterFor("x"); + x = 1; + assertTrue(a.compareAndSet(this, 1, 2)); + assertTrue(a.compareAndSet(this, 2, -4)); + assertEquals(-4, a.get(this)); + assertFalse(a.compareAndSet(this, -5, 7)); + assertEquals(-4, a.get(this)); + assertTrue(a.compareAndSet(this, -4, 7)); + assertEquals(7, a.get(this)); + } + + /** + * compareAndSet succeeds in changing protected field value if + * equal to expected else fails + */ + public void testCompareAndSetProtected() { + AtomicLongFieldUpdater a; + a = updaterFor("protectedField"); + protectedField = 1; + assertTrue(a.compareAndSet(this, 1, 2)); + assertTrue(a.compareAndSet(this, 2, -4)); + assertEquals(-4, a.get(this)); + assertFalse(a.compareAndSet(this, -5, 7)); + assertEquals(-4, a.get(this)); + assertTrue(a.compareAndSet(this, -4, 7)); + assertEquals(7, a.get(this)); + } + + /** + * compareAndSet succeeds in changing protected field value if + * equal to expected else fails + */ + public void testCompareAndSetProtectedInSubclass() { + AtomicLongFieldUpdaterTestSubclass s = + new AtomicLongFieldUpdaterTestSubclass(); + s.checkCompareAndSetProtectedSub(); + } + + /** + * compareAndSet in one thread enables another waiting for value + * to succeed + */ + public void testCompareAndSetInMultipleThreads() throws Exception { + x = 1; + final AtomicLongFieldUpdater a; + a = updaterFor("x"); + + Thread t = new Thread(new CheckedRunnable() { + public void realRun() { + while (!a.compareAndSet(AtomicLongFieldUpdaterTest.this, 2, 3)) + Thread.yield(); + }}); + + t.start(); + assertTrue(a.compareAndSet(this, 1, 2)); + t.join(LONG_DELAY_MS); + assertFalse(t.isAlive()); + assertEquals(3, a.get(this)); + } + + /** + * repeated weakCompareAndSet succeeds in changing value when equal + * to expected + */ + public void testWeakCompareAndSet() { + AtomicLongFieldUpdater a; + a = updaterFor("x"); + x = 1; + do {} while (!a.weakCompareAndSet(this, 1, 2)); + do {} while (!a.weakCompareAndSet(this, 2, -4)); + assertEquals(-4, a.get(this)); + do {} while (!a.weakCompareAndSet(this, -4, 7)); + assertEquals(7, a.get(this)); + } + + /** + * getAndSet returns previous value and sets to given value + */ + public void testGetAndSet() { + AtomicLongFieldUpdater a; + a = updaterFor("x"); + x = 1; + assertEquals(1, a.getAndSet(this, 0)); + assertEquals(0, a.getAndSet(this, -10)); + assertEquals(-10, a.getAndSet(this, 1)); + } + + /** + * getAndAdd returns previous value and adds given value + */ + public void testGetAndAdd() { + AtomicLongFieldUpdater a; + a = updaterFor("x"); + x = 1; + assertEquals(1, a.getAndAdd(this, 2)); + assertEquals(3, a.get(this)); + assertEquals(3, a.getAndAdd(this, -4)); + assertEquals(-1, a.get(this)); + } + + /** + * getAndDecrement returns previous value and decrements + */ + public void testGetAndDecrement() { + AtomicLongFieldUpdater a; + a = updaterFor("x"); + x = 1; + assertEquals(1, a.getAndDecrement(this)); + assertEquals(0, a.getAndDecrement(this)); + assertEquals(-1, a.getAndDecrement(this)); + } + + /** + * getAndIncrement returns previous value and increments + */ + public void testGetAndIncrement() { + AtomicLongFieldUpdater a; + a = updaterFor("x"); + x = 1; + assertEquals(1, a.getAndIncrement(this)); + assertEquals(2, a.get(this)); + a.set(this, -2); + assertEquals(-2, a.getAndIncrement(this)); + assertEquals(-1, a.getAndIncrement(this)); + assertEquals(0, a.getAndIncrement(this)); + assertEquals(1, a.get(this)); + } + + /** + * addAndGet adds given value to current, and returns current value + */ + public void testAddAndGet() { + AtomicLongFieldUpdater a; + a = updaterFor("x"); + x = 1; + assertEquals(3, a.addAndGet(this, 2)); + assertEquals(3, a.get(this)); + assertEquals(-1, a.addAndGet(this, -4)); + assertEquals(-1, a.get(this)); + } + + /** + * decrementAndGet decrements and returns current value + */ + public void testDecrementAndGet() { + AtomicLongFieldUpdater a; + a = updaterFor("x"); + x = 1; + assertEquals(0, a.decrementAndGet(this)); + assertEquals(-1, a.decrementAndGet(this)); + assertEquals(-2, a.decrementAndGet(this)); + assertEquals(-2, a.get(this)); + } + + /** + * incrementAndGet increments and returns current value + */ + public void testIncrementAndGet() { + AtomicLongFieldUpdater a; + a = updaterFor("x"); + x = 1; + assertEquals(2, a.incrementAndGet(this)); + assertEquals(2, a.get(this)); + a.set(this, -2); + assertEquals(-1, a.incrementAndGet(this)); + assertEquals(0, a.incrementAndGet(this)); + assertEquals(1, a.incrementAndGet(this)); + assertEquals(1, a.get(this)); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/AtomicLongTest.java b/jdk/test/java/util/concurrent/tck/AtomicLongTest.java new file mode 100644 index 00000000000..f5191af99e6 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/AtomicLongTest.java @@ -0,0 +1,297 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import java.util.concurrent.atomic.AtomicLong; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AtomicLongTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(AtomicLongTest.class); + } + + final long[] VALUES = { + Long.MIN_VALUE, + Integer.MIN_VALUE, -1, 0, 1, 42, Integer.MAX_VALUE, + Long.MAX_VALUE, + }; + + /** + * constructor initializes to given value + */ + public void testConstructor() { + AtomicLong ai = new AtomicLong(1); + assertEquals(1, ai.get()); + } + + /** + * default constructed initializes to zero + */ + public void testConstructor2() { + AtomicLong ai = new AtomicLong(); + assertEquals(0, ai.get()); + } + + /** + * get returns the last value set + */ + public void testGetSet() { + AtomicLong ai = new AtomicLong(1); + assertEquals(1, ai.get()); + ai.set(2); + assertEquals(2, ai.get()); + ai.set(-3); + assertEquals(-3, ai.get()); + } + + /** + * get returns the last value lazySet in same thread + */ + public void testGetLazySet() { + AtomicLong ai = new AtomicLong(1); + assertEquals(1, ai.get()); + ai.lazySet(2); + assertEquals(2, ai.get()); + ai.lazySet(-3); + assertEquals(-3, ai.get()); + } + + /** + * compareAndSet succeeds in changing value if equal to expected else fails + */ + public void testCompareAndSet() { + AtomicLong ai = new AtomicLong(1); + assertTrue(ai.compareAndSet(1, 2)); + assertTrue(ai.compareAndSet(2, -4)); + assertEquals(-4, ai.get()); + assertFalse(ai.compareAndSet(-5, 7)); + assertEquals(-4, ai.get()); + assertTrue(ai.compareAndSet(-4, 7)); + assertEquals(7, ai.get()); + } + + /** + * compareAndSet in one thread enables another waiting for value + * to succeed + */ + public void testCompareAndSetInMultipleThreads() throws Exception { + final AtomicLong ai = new AtomicLong(1); + Thread t = new Thread(new CheckedRunnable() { + public void realRun() { + while (!ai.compareAndSet(2, 3)) + Thread.yield(); + }}); + + t.start(); + assertTrue(ai.compareAndSet(1, 2)); + t.join(LONG_DELAY_MS); + assertFalse(t.isAlive()); + assertEquals(3, ai.get()); + } + + /** + * repeated weakCompareAndSet succeeds in changing value when equal + * to expected + */ + public void testWeakCompareAndSet() { + AtomicLong ai = new AtomicLong(1); + do {} while (!ai.weakCompareAndSet(1, 2)); + do {} while (!ai.weakCompareAndSet(2, -4)); + assertEquals(-4, ai.get()); + do {} while (!ai.weakCompareAndSet(-4, 7)); + assertEquals(7, ai.get()); + } + + /** + * getAndSet returns previous value and sets to given value + */ + public void testGetAndSet() { + AtomicLong ai = new AtomicLong(1); + assertEquals(1, ai.getAndSet(0)); + assertEquals(0, ai.getAndSet(-10)); + assertEquals(-10, ai.getAndSet(1)); + } + + /** + * getAndAdd returns previous value and adds given value + */ + public void testGetAndAdd() { + AtomicLong ai = new AtomicLong(1); + assertEquals(1, ai.getAndAdd(2)); + assertEquals(3, ai.get()); + assertEquals(3, ai.getAndAdd(-4)); + assertEquals(-1, ai.get()); + } + + /** + * getAndDecrement returns previous value and decrements + */ + public void testGetAndDecrement() { + AtomicLong ai = new AtomicLong(1); + assertEquals(1, ai.getAndDecrement()); + assertEquals(0, ai.getAndDecrement()); + assertEquals(-1, ai.getAndDecrement()); + } + + /** + * getAndIncrement returns previous value and increments + */ + public void testGetAndIncrement() { + AtomicLong ai = new AtomicLong(1); + assertEquals(1, ai.getAndIncrement()); + assertEquals(2, ai.get()); + ai.set(-2); + assertEquals(-2, ai.getAndIncrement()); + assertEquals(-1, ai.getAndIncrement()); + assertEquals(0, ai.getAndIncrement()); + assertEquals(1, ai.get()); + } + + /** + * addAndGet adds given value to current, and returns current value + */ + public void testAddAndGet() { + AtomicLong ai = new AtomicLong(1); + assertEquals(3, ai.addAndGet(2)); + assertEquals(3, ai.get()); + assertEquals(-1, ai.addAndGet(-4)); + assertEquals(-1, ai.get()); + } + + /** + * decrementAndGet decrements and returns current value + */ + public void testDecrementAndGet() { + AtomicLong ai = new AtomicLong(1); + assertEquals(0, ai.decrementAndGet()); + assertEquals(-1, ai.decrementAndGet()); + assertEquals(-2, ai.decrementAndGet()); + assertEquals(-2, ai.get()); + } + + /** + * incrementAndGet increments and returns current value + */ + public void testIncrementAndGet() { + AtomicLong ai = new AtomicLong(1); + assertEquals(2, ai.incrementAndGet()); + assertEquals(2, ai.get()); + ai.set(-2); + assertEquals(-1, ai.incrementAndGet()); + assertEquals(0, ai.incrementAndGet()); + assertEquals(1, ai.incrementAndGet()); + assertEquals(1, ai.get()); + } + + /** + * a deserialized serialized atomic holds same value + */ + public void testSerialization() throws Exception { + AtomicLong x = new AtomicLong(); + AtomicLong y = serialClone(x); + assertNotSame(x, y); + x.set(-22); + AtomicLong z = serialClone(x); + assertNotSame(y, z); + assertEquals(-22, x.get()); + assertEquals(0, y.get()); + assertEquals(-22, z.get()); + } + + /** + * toString returns current value. + */ + public void testToString() { + AtomicLong ai = new AtomicLong(); + assertEquals("0", ai.toString()); + for (long x : VALUES) { + ai.set(x); + assertEquals(Long.toString(x), ai.toString()); + } + } + + /** + * intValue returns current value. + */ + public void testIntValue() { + AtomicLong ai = new AtomicLong(); + assertEquals(0, ai.intValue()); + for (long x : VALUES) { + ai.set(x); + assertEquals((int)x, ai.intValue()); + } + } + + /** + * longValue returns current value. + */ + public void testLongValue() { + AtomicLong ai = new AtomicLong(); + assertEquals(0L, ai.longValue()); + for (long x : VALUES) { + ai.set(x); + assertEquals(x, ai.longValue()); + } + } + + /** + * floatValue returns current value. + */ + public void testFloatValue() { + AtomicLong ai = new AtomicLong(); + assertEquals(0.0f, ai.floatValue()); + for (long x : VALUES) { + ai.set(x); + assertEquals((float)x, ai.floatValue()); + } + } + + /** + * doubleValue returns current value. + */ + public void testDoubleValue() { + AtomicLong ai = new AtomicLong(); + assertEquals(0.0d, ai.doubleValue()); + for (long x : VALUES) { + ai.set(x); + assertEquals((double)x, ai.doubleValue()); + } + } + +} diff --git a/jdk/test/java/util/concurrent/tck/AtomicMarkableReferenceTest.java b/jdk/test/java/util/concurrent/tck/AtomicMarkableReferenceTest.java new file mode 100644 index 00000000000..b1c77c468d2 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/AtomicMarkableReferenceTest.java @@ -0,0 +1,180 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import java.util.concurrent.atomic.AtomicMarkableReference; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AtomicMarkableReferenceTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(AtomicMarkableReferenceTest.class); + } + + /** + * constructor initializes to given reference and mark + */ + public void testConstructor() { + AtomicMarkableReference ai = new AtomicMarkableReference(one, false); + assertSame(one, ai.getReference()); + assertFalse(ai.isMarked()); + AtomicMarkableReference a2 = new AtomicMarkableReference(null, true); + assertNull(a2.getReference()); + assertTrue(a2.isMarked()); + } + + /** + * get returns the last values of reference and mark set + */ + public void testGetSet() { + boolean[] mark = new boolean[1]; + AtomicMarkableReference ai = new AtomicMarkableReference(one, false); + assertSame(one, ai.getReference()); + assertFalse(ai.isMarked()); + assertSame(one, ai.get(mark)); + assertFalse(mark[0]); + ai.set(two, false); + assertSame(two, ai.getReference()); + assertFalse(ai.isMarked()); + assertSame(two, ai.get(mark)); + assertFalse(mark[0]); + ai.set(one, true); + assertSame(one, ai.getReference()); + assertTrue(ai.isMarked()); + assertSame(one, ai.get(mark)); + assertTrue(mark[0]); + } + + /** + * attemptMark succeeds in single thread + */ + public void testAttemptMark() { + boolean[] mark = new boolean[1]; + AtomicMarkableReference ai = new AtomicMarkableReference(one, false); + assertFalse(ai.isMarked()); + assertTrue(ai.attemptMark(one, true)); + assertTrue(ai.isMarked()); + assertSame(one, ai.get(mark)); + assertTrue(mark[0]); + } + + /** + * compareAndSet succeeds in changing values if equal to expected reference + * and mark else fails + */ + public void testCompareAndSet() { + boolean[] mark = new boolean[1]; + AtomicMarkableReference ai = new AtomicMarkableReference(one, false); + assertSame(one, ai.get(mark)); + assertFalse(ai.isMarked()); + assertFalse(mark[0]); + + assertTrue(ai.compareAndSet(one, two, false, false)); + assertSame(two, ai.get(mark)); + assertFalse(mark[0]); + + assertTrue(ai.compareAndSet(two, m3, false, true)); + assertSame(m3, ai.get(mark)); + assertTrue(mark[0]); + + assertFalse(ai.compareAndSet(two, m3, true, true)); + assertSame(m3, ai.get(mark)); + assertTrue(mark[0]); + } + + /** + * compareAndSet in one thread enables another waiting for reference value + * to succeed + */ + public void testCompareAndSetInMultipleThreads() throws Exception { + final AtomicMarkableReference ai = new AtomicMarkableReference(one, false); + Thread t = new Thread(new CheckedRunnable() { + public void realRun() { + while (!ai.compareAndSet(two, three, false, false)) + Thread.yield(); + }}); + + t.start(); + assertTrue(ai.compareAndSet(one, two, false, false)); + t.join(LONG_DELAY_MS); + assertFalse(t.isAlive()); + assertSame(three, ai.getReference()); + assertFalse(ai.isMarked()); + } + + /** + * compareAndSet in one thread enables another waiting for mark value + * to succeed + */ + public void testCompareAndSetInMultipleThreads2() throws Exception { + final AtomicMarkableReference ai = new AtomicMarkableReference(one, false); + Thread t = new Thread(new CheckedRunnable() { + public void realRun() { + while (!ai.compareAndSet(one, one, true, false)) + Thread.yield(); + }}); + + t.start(); + assertTrue(ai.compareAndSet(one, one, false, true)); + t.join(LONG_DELAY_MS); + assertFalse(t.isAlive()); + assertSame(one, ai.getReference()); + assertFalse(ai.isMarked()); + } + + /** + * repeated weakCompareAndSet succeeds in changing values when equal + * to expected + */ + public void testWeakCompareAndSet() { + boolean[] mark = new boolean[1]; + AtomicMarkableReference ai = new AtomicMarkableReference(one, false); + assertSame(one, ai.get(mark)); + assertFalse(ai.isMarked()); + assertFalse(mark[0]); + + do {} while (!ai.weakCompareAndSet(one, two, false, false)); + assertSame(two, ai.get(mark)); + assertFalse(mark[0]); + + do {} while (!ai.weakCompareAndSet(two, m3, false, true)); + assertSame(m3, ai.get(mark)); + assertTrue(mark[0]); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/AtomicReferenceArrayTest.java b/jdk/test/java/util/concurrent/tck/AtomicReferenceArrayTest.java new file mode 100644 index 00000000000..2457d40b426 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/AtomicReferenceArrayTest.java @@ -0,0 +1,246 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AtomicReferenceArrayTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(AtomicReferenceArrayTest.class); + } + + /** + * constructor creates array of given size with all elements null + */ + public void testConstructor() { + AtomicReferenceArray aa = new AtomicReferenceArray(SIZE); + for (int i = 0; i < SIZE; i++) { + assertNull(aa.get(i)); + } + } + + /** + * constructor with null array throws NPE + */ + public void testConstructor2NPE() { + try { + Integer[] a = null; + new AtomicReferenceArray(a); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * constructor with array is of same size and has all elements + */ + public void testConstructor2() { + Integer[] a = { two, one, three, four, seven }; + AtomicReferenceArray aa = new AtomicReferenceArray(a); + assertEquals(a.length, aa.length()); + for (int i = 0; i < a.length; i++) + assertEquals(a[i], aa.get(i)); + } + + /** + * Initialize AtomicReferenceArray with SubClass[] + */ + public void testConstructorSubClassArray() { + Integer[] a = { two, one, three, four, seven }; + AtomicReferenceArray aa = new AtomicReferenceArray(a); + assertEquals(a.length, aa.length()); + for (int i = 0; i < a.length; i++) { + assertSame(a[i], aa.get(i)); + Long x = Long.valueOf(i); + aa.set(i, x); + assertSame(x, aa.get(i)); + } + } + + /** + * get and set for out of bound indices throw IndexOutOfBoundsException + */ + public void testIndexing() { + AtomicReferenceArray aa = new AtomicReferenceArray(SIZE); + for (int index : new int[] { -1, SIZE }) { + try { + aa.get(index); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + try { + aa.set(index, null); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + try { + aa.lazySet(index, null); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + try { + aa.compareAndSet(index, null, null); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + try { + aa.weakCompareAndSet(index, null, null); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + } + } + + /** + * get returns the last value set at index + */ + public void testGetSet() { + AtomicReferenceArray aa = new AtomicReferenceArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.set(i, one); + assertSame(one, aa.get(i)); + aa.set(i, two); + assertSame(two, aa.get(i)); + aa.set(i, m3); + assertSame(m3, aa.get(i)); + } + } + + /** + * get returns the last value lazySet at index by same thread + */ + public void testGetLazySet() { + AtomicReferenceArray aa = new AtomicReferenceArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.lazySet(i, one); + assertSame(one, aa.get(i)); + aa.lazySet(i, two); + assertSame(two, aa.get(i)); + aa.lazySet(i, m3); + assertSame(m3, aa.get(i)); + } + } + + /** + * compareAndSet succeeds in changing value if equal to expected else fails + */ + public void testCompareAndSet() { + AtomicReferenceArray aa = new AtomicReferenceArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.set(i, one); + assertTrue(aa.compareAndSet(i, one, two)); + assertTrue(aa.compareAndSet(i, two, m4)); + assertSame(m4, aa.get(i)); + assertFalse(aa.compareAndSet(i, m5, seven)); + assertSame(m4, aa.get(i)); + assertTrue(aa.compareAndSet(i, m4, seven)); + assertSame(seven, aa.get(i)); + } + } + + /** + * compareAndSet in one thread enables another waiting for value + * to succeed + */ + public void testCompareAndSetInMultipleThreads() throws InterruptedException { + final AtomicReferenceArray a = new AtomicReferenceArray(1); + a.set(0, one); + Thread t = new Thread(new CheckedRunnable() { + public void realRun() { + while (!a.compareAndSet(0, two, three)) + Thread.yield(); + }}); + + t.start(); + assertTrue(a.compareAndSet(0, one, two)); + t.join(LONG_DELAY_MS); + assertFalse(t.isAlive()); + assertSame(three, a.get(0)); + } + + /** + * repeated weakCompareAndSet succeeds in changing value when equal + * to expected + */ + public void testWeakCompareAndSet() { + AtomicReferenceArray aa = new AtomicReferenceArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.set(i, one); + do {} while (!aa.weakCompareAndSet(i, one, two)); + do {} while (!aa.weakCompareAndSet(i, two, m4)); + assertSame(m4, aa.get(i)); + do {} while (!aa.weakCompareAndSet(i, m4, seven)); + assertSame(seven, aa.get(i)); + } + } + + /** + * getAndSet returns previous value and sets to given value at given index + */ + public void testGetAndSet() { + AtomicReferenceArray aa = new AtomicReferenceArray(SIZE); + for (int i = 0; i < SIZE; i++) { + aa.set(i, one); + assertSame(one, aa.getAndSet(i, zero)); + assertSame(zero, aa.getAndSet(i, m10)); + assertSame(m10, aa.getAndSet(i, one)); + } + } + + /** + * a deserialized serialized array holds same values + */ + public void testSerialization() throws Exception { + AtomicReferenceArray x = new AtomicReferenceArray(SIZE); + for (int i = 0; i < SIZE; i++) { + x.set(i, new Integer(-i)); + } + AtomicReferenceArray y = serialClone(x); + assertNotSame(x, y); + assertEquals(x.length(), y.length()); + for (int i = 0; i < SIZE; i++) { + assertEquals(x.get(i), y.get(i)); + } + } + + /** + * toString returns current value. + */ + public void testToString() { + Integer[] a = { two, one, three, four, seven }; + AtomicReferenceArray aa = new AtomicReferenceArray(a); + assertEquals(Arrays.toString(a), aa.toString()); + } +} diff --git a/jdk/test/java/util/concurrent/tck/AtomicReferenceFieldUpdaterTest.java b/jdk/test/java/util/concurrent/tck/AtomicReferenceFieldUpdaterTest.java new file mode 100644 index 00000000000..fb50658ea91 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/AtomicReferenceFieldUpdaterTest.java @@ -0,0 +1,265 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AtomicReferenceFieldUpdaterTest extends JSR166TestCase { + volatile Integer x = null; + protected volatile Integer protectedField; + private volatile Integer privateField; + Object z; + Integer w; + volatile int i; + + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(AtomicReferenceFieldUpdaterTest.class); + } + + // for testing subclass access + static class AtomicReferenceFieldUpdaterTestSubclass extends AtomicReferenceFieldUpdaterTest { + public void checkPrivateAccess() { + try { + AtomicReferenceFieldUpdater a = + AtomicReferenceFieldUpdater.newUpdater + (AtomicReferenceFieldUpdaterTest.class, Integer.class, "privateField"); + shouldThrow(); + } catch (RuntimeException success) { + assertNotNull(success.getCause()); + } + } + + public void checkCompareAndSetProtectedSub() { + AtomicReferenceFieldUpdater a = + AtomicReferenceFieldUpdater.newUpdater + (AtomicReferenceFieldUpdaterTest.class, Integer.class, "protectedField"); + this.protectedField = one; + assertTrue(a.compareAndSet(this, one, two)); + assertTrue(a.compareAndSet(this, two, m4)); + assertSame(m4, a.get(this)); + assertFalse(a.compareAndSet(this, m5, seven)); + assertFalse(seven == a.get(this)); + assertTrue(a.compareAndSet(this, m4, seven)); + assertSame(seven, a.get(this)); + } + } + + static class UnrelatedClass { + public void checkPackageAccess(AtomicReferenceFieldUpdaterTest obj) { + obj.x = one; + AtomicReferenceFieldUpdater a = + AtomicReferenceFieldUpdater.newUpdater + (AtomicReferenceFieldUpdaterTest.class, Integer.class, "x"); + assertSame(one, a.get(obj)); + assertTrue(a.compareAndSet(obj, one, two)); + assertSame(two, a.get(obj)); + } + + public void checkPrivateAccess(AtomicReferenceFieldUpdaterTest obj) { + try { + AtomicReferenceFieldUpdater a = + AtomicReferenceFieldUpdater.newUpdater + (AtomicReferenceFieldUpdaterTest.class, Integer.class, "privateField"); + throw new AssertionError("should throw"); + } catch (RuntimeException success) { + assertNotNull(success.getCause()); + } + } + } + + static AtomicReferenceFieldUpdater updaterFor(String fieldName) { + return AtomicReferenceFieldUpdater.newUpdater + (AtomicReferenceFieldUpdaterTest.class, Integer.class, fieldName); + } + + /** + * Construction with non-existent field throws RuntimeException + */ + public void testConstructor() { + try { + updaterFor("y"); + shouldThrow(); + } catch (RuntimeException success) { + assertNotNull(success.getCause()); + } + } + + /** + * construction with field not of given type throws ClassCastException + */ + public void testConstructor2() { + try { + updaterFor("z"); + shouldThrow(); + } catch (ClassCastException success) {} + } + + /** + * Constructor with non-volatile field throws IllegalArgumentException + */ + public void testConstructor3() { + try { + updaterFor("w"); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor with non-reference field throws ClassCastException + */ + public void testConstructor4() { + try { + updaterFor("i"); + shouldThrow(); + } catch (ClassCastException success) {} + } + + /** + * construction using private field from subclass throws RuntimeException + */ + public void testPrivateFieldInSubclass() { + AtomicReferenceFieldUpdaterTestSubclass s = + new AtomicReferenceFieldUpdaterTestSubclass(); + s.checkPrivateAccess(); + } + + /** + * construction from unrelated class; package access is allowed, + * private access is not + */ + public void testUnrelatedClassAccess() { + new UnrelatedClass().checkPackageAccess(this); + new UnrelatedClass().checkPrivateAccess(this); + } + + /** + * get returns the last value set or assigned + */ + public void testGetSet() { + AtomicReferenceFieldUpdater a; + a = updaterFor("x"); + x = one; + assertSame(one, a.get(this)); + a.set(this, two); + assertSame(two, a.get(this)); + a.set(this, m3); + assertSame(m3, a.get(this)); + } + + /** + * get returns the last value lazySet by same thread + */ + public void testGetLazySet() { + AtomicReferenceFieldUpdater a; + a = updaterFor("x"); + x = one; + assertSame(one, a.get(this)); + a.lazySet(this, two); + assertSame(two, a.get(this)); + a.lazySet(this, m3); + assertSame(m3, a.get(this)); + } + + /** + * compareAndSet succeeds in changing value if equal to expected else fails + */ + public void testCompareAndSet() { + AtomicReferenceFieldUpdater a; + a = updaterFor("x"); + x = one; + assertTrue(a.compareAndSet(this, one, two)); + assertTrue(a.compareAndSet(this, two, m4)); + assertSame(m4, a.get(this)); + assertFalse(a.compareAndSet(this, m5, seven)); + assertFalse(seven == a.get(this)); + assertTrue(a.compareAndSet(this, m4, seven)); + assertSame(seven, a.get(this)); + } + + /** + * compareAndSet in one thread enables another waiting for value + * to succeed + */ + public void testCompareAndSetInMultipleThreads() throws Exception { + x = one; + final AtomicReferenceFieldUpdater a; + a = updaterFor("x"); + + Thread t = new Thread(new CheckedRunnable() { + public void realRun() { + while (!a.compareAndSet(AtomicReferenceFieldUpdaterTest.this, two, three)) + Thread.yield(); + }}); + + t.start(); + assertTrue(a.compareAndSet(this, one, two)); + t.join(LONG_DELAY_MS); + assertFalse(t.isAlive()); + assertSame(three, a.get(this)); + } + + /** + * repeated weakCompareAndSet succeeds in changing value when equal + * to expected + */ + public void testWeakCompareAndSet() { + AtomicReferenceFieldUpdater a; + a = updaterFor("x"); + x = one; + do {} while (!a.weakCompareAndSet(this, one, two)); + do {} while (!a.weakCompareAndSet(this, two, m4)); + assertSame(m4, a.get(this)); + do {} while (!a.weakCompareAndSet(this, m4, seven)); + assertSame(seven, a.get(this)); + } + + /** + * getAndSet returns previous value and sets to given value + */ + public void testGetAndSet() { + AtomicReferenceFieldUpdater a; + a = updaterFor("x"); + x = one; + assertSame(one, a.getAndSet(this, zero)); + assertSame(zero, a.getAndSet(this, m10)); + assertSame(m10, a.getAndSet(this, 1)); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/AtomicReferenceTest.java b/jdk/test/java/util/concurrent/tck/AtomicReferenceTest.java new file mode 100644 index 00000000000..bae2a43906a --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/AtomicReferenceTest.java @@ -0,0 +1,170 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import java.util.concurrent.atomic.AtomicReference; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AtomicReferenceTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(AtomicReferenceTest.class); + } + + /** + * constructor initializes to given value + */ + public void testConstructor() { + AtomicReference ai = new AtomicReference(one); + assertSame(one, ai.get()); + } + + /** + * default constructed initializes to null + */ + public void testConstructor2() { + AtomicReference ai = new AtomicReference(); + assertNull(ai.get()); + } + + /** + * get returns the last value set + */ + public void testGetSet() { + AtomicReference ai = new AtomicReference(one); + assertSame(one, ai.get()); + ai.set(two); + assertSame(two, ai.get()); + ai.set(m3); + assertSame(m3, ai.get()); + } + + /** + * get returns the last value lazySet in same thread + */ + public void testGetLazySet() { + AtomicReference ai = new AtomicReference(one); + assertSame(one, ai.get()); + ai.lazySet(two); + assertSame(two, ai.get()); + ai.lazySet(m3); + assertSame(m3, ai.get()); + } + + /** + * compareAndSet succeeds in changing value if equal to expected else fails + */ + public void testCompareAndSet() { + AtomicReference ai = new AtomicReference(one); + assertTrue(ai.compareAndSet(one, two)); + assertTrue(ai.compareAndSet(two, m4)); + assertSame(m4, ai.get()); + assertFalse(ai.compareAndSet(m5, seven)); + assertSame(m4, ai.get()); + assertTrue(ai.compareAndSet(m4, seven)); + assertSame(seven, ai.get()); + } + + /** + * compareAndSet in one thread enables another waiting for value + * to succeed + */ + public void testCompareAndSetInMultipleThreads() throws Exception { + final AtomicReference ai = new AtomicReference(one); + Thread t = new Thread(new CheckedRunnable() { + public void realRun() { + while (!ai.compareAndSet(two, three)) + Thread.yield(); + }}); + + t.start(); + assertTrue(ai.compareAndSet(one, two)); + t.join(LONG_DELAY_MS); + assertFalse(t.isAlive()); + assertSame(three, ai.get()); + } + + /** + * repeated weakCompareAndSet succeeds in changing value when equal + * to expected + */ + public void testWeakCompareAndSet() { + AtomicReference ai = new AtomicReference(one); + do {} while (!ai.weakCompareAndSet(one, two)); + do {} while (!ai.weakCompareAndSet(two, m4)); + assertSame(m4, ai.get()); + do {} while (!ai.weakCompareAndSet(m4, seven)); + assertSame(seven, ai.get()); + } + + /** + * getAndSet returns previous value and sets to given value + */ + public void testGetAndSet() { + AtomicReference ai = new AtomicReference(one); + assertSame(one, ai.getAndSet(zero)); + assertSame(zero, ai.getAndSet(m10)); + assertSame(m10, ai.getAndSet(one)); + } + + /** + * a deserialized serialized atomic holds same value + */ + public void testSerialization() throws Exception { + AtomicReference x = new AtomicReference(); + AtomicReference y = serialClone(x); + assertNotSame(x, y); + x.set(one); + AtomicReference z = serialClone(x); + assertNotSame(y, z); + assertEquals(one, x.get()); + assertEquals(null, y.get()); + assertEquals(one, z.get()); + } + + /** + * toString returns current value. + */ + public void testToString() { + AtomicReference ai = new AtomicReference(one); + assertEquals(one.toString(), ai.toString()); + ai.set(two); + assertEquals(two.toString(), ai.toString()); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/AtomicStampedReferenceTest.java b/jdk/test/java/util/concurrent/tck/AtomicStampedReferenceTest.java new file mode 100644 index 00000000000..1a352130d3e --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/AtomicStampedReferenceTest.java @@ -0,0 +1,180 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import java.util.concurrent.atomic.AtomicStampedReference; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AtomicStampedReferenceTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(AtomicStampedReferenceTest.class); + } + + /** + * constructor initializes to given reference and stamp + */ + public void testConstructor() { + AtomicStampedReference ai = new AtomicStampedReference(one, 0); + assertSame(one, ai.getReference()); + assertEquals(0, ai.getStamp()); + AtomicStampedReference a2 = new AtomicStampedReference(null, 1); + assertNull(a2.getReference()); + assertEquals(1, a2.getStamp()); + } + + /** + * get returns the last values of reference and stamp set + */ + public void testGetSet() { + int[] mark = new int[1]; + AtomicStampedReference ai = new AtomicStampedReference(one, 0); + assertSame(one, ai.getReference()); + assertEquals(0, ai.getStamp()); + assertSame(one, ai.get(mark)); + assertEquals(0, mark[0]); + ai.set(two, 0); + assertSame(two, ai.getReference()); + assertEquals(0, ai.getStamp()); + assertSame(two, ai.get(mark)); + assertEquals(0, mark[0]); + ai.set(one, 1); + assertSame(one, ai.getReference()); + assertEquals(1, ai.getStamp()); + assertSame(one, ai.get(mark)); + assertEquals(1, mark[0]); + } + + /** + * attemptStamp succeeds in single thread + */ + public void testAttemptStamp() { + int[] mark = new int[1]; + AtomicStampedReference ai = new AtomicStampedReference(one, 0); + assertEquals(0, ai.getStamp()); + assertTrue(ai.attemptStamp(one, 1)); + assertEquals(1, ai.getStamp()); + assertSame(one, ai.get(mark)); + assertEquals(1, mark[0]); + } + + /** + * compareAndSet succeeds in changing values if equal to expected reference + * and stamp else fails + */ + public void testCompareAndSet() { + int[] mark = new int[1]; + AtomicStampedReference ai = new AtomicStampedReference(one, 0); + assertSame(one, ai.get(mark)); + assertEquals(0, ai.getStamp()); + assertEquals(0, mark[0]); + + assertTrue(ai.compareAndSet(one, two, 0, 0)); + assertSame(two, ai.get(mark)); + assertEquals(0, mark[0]); + + assertTrue(ai.compareAndSet(two, m3, 0, 1)); + assertSame(m3, ai.get(mark)); + assertEquals(1, mark[0]); + + assertFalse(ai.compareAndSet(two, m3, 1, 1)); + assertSame(m3, ai.get(mark)); + assertEquals(1, mark[0]); + } + + /** + * compareAndSet in one thread enables another waiting for reference value + * to succeed + */ + public void testCompareAndSetInMultipleThreads() throws Exception { + final AtomicStampedReference ai = new AtomicStampedReference(one, 0); + Thread t = new Thread(new CheckedRunnable() { + public void realRun() { + while (!ai.compareAndSet(two, three, 0, 0)) + Thread.yield(); + }}); + + t.start(); + assertTrue(ai.compareAndSet(one, two, 0, 0)); + t.join(LONG_DELAY_MS); + assertFalse(t.isAlive()); + assertSame(three, ai.getReference()); + assertEquals(0, ai.getStamp()); + } + + /** + * compareAndSet in one thread enables another waiting for stamp value + * to succeed + */ + public void testCompareAndSetInMultipleThreads2() throws Exception { + final AtomicStampedReference ai = new AtomicStampedReference(one, 0); + Thread t = new Thread(new CheckedRunnable() { + public void realRun() { + while (!ai.compareAndSet(one, one, 1, 2)) + Thread.yield(); + }}); + + t.start(); + assertTrue(ai.compareAndSet(one, one, 0, 1)); + t.join(LONG_DELAY_MS); + assertFalse(t.isAlive()); + assertSame(one, ai.getReference()); + assertEquals(2, ai.getStamp()); + } + + /** + * repeated weakCompareAndSet succeeds in changing values when equal + * to expected + */ + public void testWeakCompareAndSet() { + int[] mark = new int[1]; + AtomicStampedReference ai = new AtomicStampedReference(one, 0); + assertSame(one, ai.get(mark)); + assertEquals(0, ai.getStamp()); + assertEquals(0, mark[0]); + + do {} while (!ai.weakCompareAndSet(one, two, 0, 0)); + assertSame(two, ai.get(mark)); + assertEquals(0, mark[0]); + + do {} while (!ai.weakCompareAndSet(two, m3, 0, 1)); + assertSame(m3, ai.get(mark)); + assertEquals(1, mark[0]); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/BlockingQueueTest.java b/jdk/test/java/util/concurrent/tck/BlockingQueueTest.java new file mode 100644 index 00000000000..aa38b3bd735 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/BlockingQueueTest.java @@ -0,0 +1,403 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea and Martin Buchholz with assistance from members + * of JCP JSR-166 Expert Group and released to the public domain, as + * explained at http://creativecommons.org/publicdomain/zero/1.0/ + * + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Queue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * Contains "contract" tests applicable to all BlockingQueue implementations. + */ +public abstract class BlockingQueueTest extends JSR166TestCase { + /* + * This is the start of an attempt to refactor the tests for the + * various related implementations of related interfaces without + * too much duplicated code. junit does not really support such + * testing. Here subclasses of TestCase not only contain tests, + * but also configuration information that describes the + * implementation class, most importantly how to instantiate + * instances. + */ + + /** Like suite(), but non-static */ + public Test testSuite() { + // TODO: filter the returned tests using the configuration + // information provided by the subclass via protected methods. + return new TestSuite(this.getClass()); + } + + //---------------------------------------------------------------- + // Configuration methods + //---------------------------------------------------------------- + + /** Returns an empty instance of the implementation class. */ + protected abstract BlockingQueue emptyCollection(); + + /** + * Returns an element suitable for insertion in the collection. + * Override for collections with unusual element types. + */ + protected Object makeElement(int i) { + return Integer.valueOf(i); + } + + //---------------------------------------------------------------- + // Tests + //---------------------------------------------------------------- + + /** + * offer(null) throws NullPointerException + */ + public void testOfferNull() { + final Queue q = emptyCollection(); + try { + q.offer(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * add(null) throws NullPointerException + */ + public void testAddNull() { + final Collection q = emptyCollection(); + try { + q.add(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * timed offer(null) throws NullPointerException + */ + public void testTimedOfferNull() throws InterruptedException { + final BlockingQueue q = emptyCollection(); + long startTime = System.nanoTime(); + try { + q.offer(null, LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + + /** + * put(null) throws NullPointerException + */ + public void testPutNull() throws InterruptedException { + final BlockingQueue q = emptyCollection(); + try { + q.put(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * put(null) throws NullPointerException + */ + public void testAddAllNull() throws InterruptedException { + final Collection q = emptyCollection(); + try { + q.addAll(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll of a collection with null elements throws NullPointerException + */ + public void testAddAllNullElements() { + final Collection q = emptyCollection(); + final Collection elements = Arrays.asList(new Integer[SIZE]); + try { + q.addAll(elements); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * toArray(null) throws NullPointerException + */ + public void testToArray_NullArray() { + final Collection q = emptyCollection(); + try { + q.toArray(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * drainTo(null) throws NullPointerException + */ + public void testDrainToNull() { + final BlockingQueue q = emptyCollection(); + try { + q.drainTo(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * drainTo(this) throws IllegalArgumentException + */ + public void testDrainToSelf() { + final BlockingQueue q = emptyCollection(); + try { + q.drainTo(q); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * drainTo(null, n) throws NullPointerException + */ + public void testDrainToNullN() { + final BlockingQueue q = emptyCollection(); + try { + q.drainTo(null, 0); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * drainTo(this, n) throws IllegalArgumentException + */ + public void testDrainToSelfN() { + final BlockingQueue q = emptyCollection(); + try { + q.drainTo(q, 0); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * drainTo(c, n) returns 0 and does nothing when n <= 0 + */ + public void testDrainToNonPositiveMaxElements() { + final BlockingQueue q = emptyCollection(); + final int[] ns = { 0, -1, -42, Integer.MIN_VALUE }; + for (int n : ns) + assertEquals(0, q.drainTo(new ArrayList(), n)); + if (q.remainingCapacity() > 0) { + // Not SynchronousQueue, that is + Object one = makeElement(1); + q.add(one); + ArrayList c = new ArrayList(); + for (int n : ns) + assertEquals(0, q.drainTo(new ArrayList(), n)); + assertEquals(1, q.size()); + assertSame(one, q.poll()); + assertTrue(c.isEmpty()); + } + } + + /** + * timed poll before a delayed offer times out; after offer succeeds; + * on interruption throws + */ + public void testTimedPollWithOffer() throws InterruptedException { + final BlockingQueue q = emptyCollection(); + final CheckedBarrier barrier = new CheckedBarrier(2); + final Object zero = makeElement(0); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + assertNull(q.poll(timeoutMillis(), MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + + barrier.await(); + + assertSame(zero, q.poll(LONG_DELAY_MS, MILLISECONDS)); + + Thread.currentThread().interrupt(); + try { + q.poll(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + barrier.await(); + try { + q.poll(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + }}); + + barrier.await(); + long startTime = System.nanoTime(); + assertTrue(q.offer(zero, LONG_DELAY_MS, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + + barrier.await(); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * take() blocks interruptibly when empty + */ + public void testTakeFromEmptyBlocksInterruptibly() { + final BlockingQueue q = emptyCollection(); + final CountDownLatch threadStarted = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + threadStarted.countDown(); + try { + q.take(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(threadStarted); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * take() throws InterruptedException immediately if interrupted + * before waiting + */ + public void testTakeFromEmptyAfterInterrupt() { + final BlockingQueue q = emptyCollection(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + Thread.currentThread().interrupt(); + try { + q.take(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + awaitTermination(t); + } + + /** + * timed poll() blocks interruptibly when empty + */ + public void testTimedPollFromEmptyBlocksInterruptibly() { + final BlockingQueue q = emptyCollection(); + final CountDownLatch threadStarted = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + threadStarted.countDown(); + try { + q.poll(2 * LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(threadStarted); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * timed poll() throws InterruptedException immediately if + * interrupted before waiting + */ + public void testTimedPollFromEmptyAfterInterrupt() { + final BlockingQueue q = emptyCollection(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + Thread.currentThread().interrupt(); + try { + q.poll(2 * LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + awaitTermination(t); + } + + /** + * remove(x) removes x and returns true if present + * TODO: move to superclass CollectionTest.java + */ + public void testRemoveElement() { + final BlockingQueue q = emptyCollection(); + final int size = Math.min(q.remainingCapacity(), SIZE); + final Object[] elts = new Object[size]; + assertFalse(q.contains(makeElement(99))); + assertFalse(q.remove(makeElement(99))); + checkEmpty(q); + for (int i = 0; i < size; i++) + q.add(elts[i] = makeElement(i)); + for (int i = 1; i < size; i += 2) { + for (int pass = 0; pass < 2; pass++) { + assertEquals((pass == 0), q.contains(elts[i])); + assertEquals((pass == 0), q.remove(elts[i])); + assertFalse(q.contains(elts[i])); + assertTrue(q.contains(elts[i - 1])); + if (i < size - 1) + assertTrue(q.contains(elts[i + 1])); + } + } + if (size > 0) + assertTrue(q.contains(elts[0])); + for (int i = size - 2; i >= 0; i -= 2) { + assertTrue(q.contains(elts[i])); + assertFalse(q.contains(elts[i + 1])); + assertTrue(q.remove(elts[i])); + assertFalse(q.contains(elts[i])); + assertFalse(q.remove(elts[i + 1])); + assertFalse(q.contains(elts[i + 1])); + } + checkEmpty(q); + } + + /** For debugging. */ + public void XXXXtestFails() { + fail(emptyCollection().getClass().toString()); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/Collection8Test.java b/jdk/test/java/util/concurrent/tck/Collection8Test.java new file mode 100644 index 00000000000..1a10b280228 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/Collection8Test.java @@ -0,0 +1,124 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea and Martin Buchholz with assistance from + * members of JCP JSR-166 Expert Group and released to the public + * domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; + +import junit.framework.Test; + +/** + * Contains tests applicable to all jdk8+ Collection implementations. + * An extension of CollectionTest. + */ +public class Collection8Test extends JSR166TestCase { + final CollectionImplementation impl; + + /** Tests are parameterized by a Collection implementation. */ + Collection8Test(CollectionImplementation impl, String methodName) { + super(methodName); + this.impl = impl; + } + + public static Test testSuite(CollectionImplementation impl) { + return parameterizedTestSuite(Collection8Test.class, + CollectionImplementation.class, + impl); + } + + /** + * stream().forEach returns elements in the collection + */ + public void testForEach() throws Throwable { + final Collection c = impl.emptyCollection(); + final AtomicLong count = new AtomicLong(0L); + final Object x = impl.makeElement(1); + final Object y = impl.makeElement(2); + final ArrayList found = new ArrayList(); + Consumer spy = (o) -> { found.add(o); }; + c.stream().forEach(spy); + assertTrue(found.isEmpty()); + + assertTrue(c.add(x)); + c.stream().forEach(spy); + assertEquals(Collections.singletonList(x), found); + found.clear(); + + assertTrue(c.add(y)); + c.stream().forEach(spy); + assertEquals(2, found.size()); + assertTrue(found.contains(x)); + assertTrue(found.contains(y)); + found.clear(); + + c.clear(); + c.stream().forEach(spy); + assertTrue(found.isEmpty()); + } + + public void testForEachConcurrentStressTest() throws Throwable { + if (!impl.isConcurrent()) return; + final Collection c = impl.emptyCollection(); + final long testDurationMillis = SHORT_DELAY_MS; + final AtomicBoolean done = new AtomicBoolean(false); + final Object elt = impl.makeElement(1); + ExecutorService pool = Executors.newCachedThreadPool(); + Runnable checkElt = () -> { + while (!done.get()) + c.stream().forEach((x) -> { assertSame(x, elt); }); }; + Runnable addRemove = () -> { + while (!done.get()) { + assertTrue(c.add(elt)); + assertTrue(c.remove(elt)); + }}; + Future f1 = pool.submit(checkElt); + Future f2 = pool.submit(addRemove); + Thread.sleep(testDurationMillis); + done.set(true); + pool.shutdown(); + assertTrue(pool.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertNull(f1.get(LONG_DELAY_MS, MILLISECONDS)); + assertNull(f2.get(LONG_DELAY_MS, MILLISECONDS)); + } + + // public void testCollection8DebugFail() { fail(); } +} diff --git a/jdk/test/java/util/concurrent/tck/CollectionImplementation.java b/jdk/test/java/util/concurrent/tck/CollectionImplementation.java new file mode 100644 index 00000000000..54ffb7c0921 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/CollectionImplementation.java @@ -0,0 +1,46 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea and Martin Buchholz with assistance from + * members of JCP JSR-166 Expert Group and released to the public + * domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import java.util.Collection; + +/** Allows tests to work with different Collection implementations. */ +public interface CollectionImplementation { + /** Returns the Collection class. */ + public Class klazz(); + /** Returns an empty collection. */ + public Collection emptyCollection(); + public Object makeElement(int i); + public boolean isConcurrent(); + public boolean permitsNulls(); +} diff --git a/jdk/test/java/util/concurrent/tck/CollectionTest.java b/jdk/test/java/util/concurrent/tck/CollectionTest.java new file mode 100644 index 00000000000..7b532af8019 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/CollectionTest.java @@ -0,0 +1,68 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea and Martin Buchholz with assistance from + * members of JCP JSR-166 Expert Group and released to the public + * domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import java.util.Collection; + +import junit.framework.Test; + +/** + * Contains tests applicable to all Collection implementations. + */ +public class CollectionTest extends JSR166TestCase { + final CollectionImplementation impl; + + /** Tests are parameterized by a Collection implementation. */ + CollectionTest(CollectionImplementation impl, String methodName) { + super(methodName); + this.impl = impl; + } + + public static Test testSuite(CollectionImplementation impl) { + return newTestSuite + (parameterizedTestSuite(CollectionTest.class, + CollectionImplementation.class, + impl), + jdk8ParameterizedTestSuite(CollectionTest.class, + CollectionImplementation.class, + impl)); + } + + /** A test of the CollectionImplementation implementation ! */ + public void testEmptyMeansEmpty() { + assertTrue(impl.emptyCollection().isEmpty()); + assertEquals(0, impl.emptyCollection().size()); + } + + // public void testCollectionDebugFail() { fail(); } +} diff --git a/jdk/test/java/util/concurrent/tck/CompletableFutureTest.java b/jdk/test/java/util/concurrent/tck/CompletableFutureTest.java new file mode 100644 index 00000000000..a74a0403f2f --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/CompletableFutureTest.java @@ -0,0 +1,3980 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea and Martin Buchholz with assistance from + * members of JCP JSR-166 Expert Group and released to the public + * domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.CompletableFuture.failedFuture; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinTask; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestSuite; + +public class CompletableFutureTest extends JSR166TestCase { + + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(CompletableFutureTest.class); + } + + static class CFException extends RuntimeException {} + + void checkIncomplete(CompletableFuture f) { + assertFalse(f.isDone()); + assertFalse(f.isCancelled()); + assertTrue(f.toString().contains("Not completed")); + try { + assertNull(f.getNow(null)); + } catch (Throwable fail) { threadUnexpectedException(fail); } + try { + f.get(0L, SECONDS); + shouldThrow(); + } + catch (TimeoutException success) {} + catch (Throwable fail) { threadUnexpectedException(fail); } + } + + void checkCompletedNormally(CompletableFuture f, T value) { + checkTimedGet(f, value); + + try { + assertEquals(value, f.join()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + try { + assertEquals(value, f.getNow(null)); + } catch (Throwable fail) { threadUnexpectedException(fail); } + try { + assertEquals(value, f.get()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + assertTrue(f.isDone()); + assertFalse(f.isCancelled()); + assertFalse(f.isCompletedExceptionally()); + assertTrue(f.toString().contains("[Completed normally]")); + } + + /** + * Returns the "raw" internal exceptional completion of f, + * without any additional wrapping with CompletionException. + */ + Throwable exceptionalCompletion(CompletableFuture f) { + // handle (and whenComplete) can distinguish between "direct" + // and "wrapped" exceptional completion + return f.handle((U u, Throwable t) -> t).join(); + } + + void checkCompletedExceptionally(CompletableFuture f, + boolean wrapped, + Consumer checker) { + Throwable cause = exceptionalCompletion(f); + if (wrapped) { + assertTrue(cause instanceof CompletionException); + cause = cause.getCause(); + } + checker.accept(cause); + + long startTime = System.nanoTime(); + try { + f.get(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(cause, success.getCause()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS / 2); + + try { + f.join(); + shouldThrow(); + } catch (CompletionException success) { + assertSame(cause, success.getCause()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + + try { + f.getNow(null); + shouldThrow(); + } catch (CompletionException success) { + assertSame(cause, success.getCause()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + + try { + f.get(); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(cause, success.getCause()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + + assertFalse(f.isCancelled()); + assertTrue(f.isDone()); + assertTrue(f.isCompletedExceptionally()); + assertTrue(f.toString().contains("[Completed exceptionally]")); + } + + void checkCompletedWithWrappedCFException(CompletableFuture f) { + checkCompletedExceptionally(f, true, + (t) -> assertTrue(t instanceof CFException)); + } + + void checkCompletedWithWrappedCancellationException(CompletableFuture f) { + checkCompletedExceptionally(f, true, + (t) -> assertTrue(t instanceof CancellationException)); + } + + void checkCompletedWithTimeoutException(CompletableFuture f) { + checkCompletedExceptionally(f, false, + (t) -> assertTrue(t instanceof TimeoutException)); + } + + void checkCompletedWithWrappedException(CompletableFuture f, + Throwable ex) { + checkCompletedExceptionally(f, true, (t) -> assertSame(t, ex)); + } + + void checkCompletedExceptionally(CompletableFuture f, Throwable ex) { + checkCompletedExceptionally(f, false, (t) -> assertSame(t, ex)); + } + + void checkCancelled(CompletableFuture f) { + long startTime = System.nanoTime(); + try { + f.get(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (CancellationException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS / 2); + + try { + f.join(); + shouldThrow(); + } catch (CancellationException success) {} + try { + f.getNow(null); + shouldThrow(); + } catch (CancellationException success) {} + try { + f.get(); + shouldThrow(); + } catch (CancellationException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + + assertTrue(exceptionalCompletion(f) instanceof CancellationException); + + assertTrue(f.isDone()); + assertTrue(f.isCompletedExceptionally()); + assertTrue(f.isCancelled()); + assertTrue(f.toString().contains("[Completed exceptionally]")); + } + + /** + * A newly constructed CompletableFuture is incomplete, as indicated + * by methods isDone, isCancelled, and getNow + */ + public void testConstructor() { + CompletableFuture f = new CompletableFuture<>(); + checkIncomplete(f); + } + + /** + * complete completes normally, as indicated by methods isDone, + * isCancelled, join, get, and getNow + */ + public void testComplete() { + for (Integer v1 : new Integer[] { 1, null }) + { + CompletableFuture f = new CompletableFuture<>(); + checkIncomplete(f); + assertTrue(f.complete(v1)); + assertFalse(f.complete(v1)); + checkCompletedNormally(f, v1); + }} + + /** + * completeExceptionally completes exceptionally, as indicated by + * methods isDone, isCancelled, join, get, and getNow + */ + public void testCompleteExceptionally() { + CompletableFuture f = new CompletableFuture<>(); + CFException ex = new CFException(); + checkIncomplete(f); + f.completeExceptionally(ex); + checkCompletedExceptionally(f, ex); + } + + /** + * cancel completes exceptionally and reports cancelled, as indicated by + * methods isDone, isCancelled, join, get, and getNow + */ + public void testCancel() { + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + { + CompletableFuture f = new CompletableFuture<>(); + checkIncomplete(f); + assertTrue(f.cancel(mayInterruptIfRunning)); + assertTrue(f.cancel(mayInterruptIfRunning)); + assertTrue(f.cancel(!mayInterruptIfRunning)); + checkCancelled(f); + }} + + /** + * obtrudeValue forces completion with given value + */ + public void testObtrudeValue() { + CompletableFuture f = new CompletableFuture<>(); + checkIncomplete(f); + assertTrue(f.complete(one)); + checkCompletedNormally(f, one); + f.obtrudeValue(three); + checkCompletedNormally(f, three); + f.obtrudeValue(two); + checkCompletedNormally(f, two); + f = new CompletableFuture<>(); + f.obtrudeValue(three); + checkCompletedNormally(f, three); + f.obtrudeValue(null); + checkCompletedNormally(f, null); + f = new CompletableFuture<>(); + f.completeExceptionally(new CFException()); + f.obtrudeValue(four); + checkCompletedNormally(f, four); + } + + /** + * obtrudeException forces completion with given exception + */ + public void testObtrudeException() { + for (Integer v1 : new Integer[] { 1, null }) + { + CFException ex; + CompletableFuture f; + + f = new CompletableFuture<>(); + assertTrue(f.complete(v1)); + for (int i = 0; i < 2; i++) { + f.obtrudeException(ex = new CFException()); + checkCompletedExceptionally(f, ex); + } + + f = new CompletableFuture<>(); + for (int i = 0; i < 2; i++) { + f.obtrudeException(ex = new CFException()); + checkCompletedExceptionally(f, ex); + } + + f = new CompletableFuture<>(); + f.completeExceptionally(ex = new CFException()); + f.obtrudeValue(v1); + checkCompletedNormally(f, v1); + f.obtrudeException(ex = new CFException()); + checkCompletedExceptionally(f, ex); + f.completeExceptionally(new CFException()); + checkCompletedExceptionally(f, ex); + assertFalse(f.complete(v1)); + checkCompletedExceptionally(f, ex); + }} + + /** + * getNumberOfDependents returns number of dependent tasks + */ + public void testGetNumberOfDependents() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + { + CompletableFuture f = new CompletableFuture<>(); + assertEquals(0, f.getNumberOfDependents()); + final CompletableFuture g = m.thenRun(f, new Noop(m)); + assertEquals(1, f.getNumberOfDependents()); + assertEquals(0, g.getNumberOfDependents()); + final CompletableFuture h = m.thenRun(f, new Noop(m)); + assertEquals(2, f.getNumberOfDependents()); + assertEquals(0, h.getNumberOfDependents()); + assertTrue(f.complete(v1)); + checkCompletedNormally(g, null); + checkCompletedNormally(h, null); + assertEquals(0, f.getNumberOfDependents()); + assertEquals(0, g.getNumberOfDependents()); + assertEquals(0, h.getNumberOfDependents()); + }} + + /** + * toString indicates current completion state + */ + public void testToString() { + CompletableFuture f; + + f = new CompletableFuture(); + assertTrue(f.toString().contains("[Not completed]")); + + assertTrue(f.complete("foo")); + assertTrue(f.toString().contains("[Completed normally]")); + + f = new CompletableFuture(); + assertTrue(f.completeExceptionally(new IndexOutOfBoundsException())); + assertTrue(f.toString().contains("[Completed exceptionally]")); + + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) { + f = new CompletableFuture(); + assertTrue(f.cancel(mayInterruptIfRunning)); + assertTrue(f.toString().contains("[Completed exceptionally]")); + } + } + + /** + * completedFuture returns a completed CompletableFuture with given value + */ + public void testCompletedFuture() { + CompletableFuture f = CompletableFuture.completedFuture("test"); + checkCompletedNormally(f, "test"); + } + + abstract class CheckedAction { + int invocationCount = 0; + final ExecutionMode m; + CheckedAction(ExecutionMode m) { this.m = m; } + void invoked() { + m.checkExecutionMode(); + assertEquals(0, invocationCount++); + } + void assertNotInvoked() { assertEquals(0, invocationCount); } + void assertInvoked() { assertEquals(1, invocationCount); } + } + + abstract class CheckedIntegerAction extends CheckedAction { + Integer value; + CheckedIntegerAction(ExecutionMode m) { super(m); } + void assertValue(Integer expected) { + assertInvoked(); + assertEquals(expected, value); + } + } + + class IntegerSupplier extends CheckedAction + implements Supplier + { + final Integer value; + IntegerSupplier(ExecutionMode m, Integer value) { + super(m); + this.value = value; + } + public Integer get() { + invoked(); + return value; + } + } + + // A function that handles and produces null values as well. + static Integer inc(Integer x) { + return (x == null) ? null : x + 1; + } + + class NoopConsumer extends CheckedIntegerAction + implements Consumer + { + NoopConsumer(ExecutionMode m) { super(m); } + public void accept(Integer x) { + invoked(); + value = x; + } + } + + class IncFunction extends CheckedIntegerAction + implements Function + { + IncFunction(ExecutionMode m) { super(m); } + public Integer apply(Integer x) { + invoked(); + return value = inc(x); + } + } + + // Choose non-commutative actions for better coverage + // A non-commutative function that handles and produces null values as well. + static Integer subtract(Integer x, Integer y) { + return (x == null && y == null) ? null : + ((x == null) ? 42 : x.intValue()) + - ((y == null) ? 99 : y.intValue()); + } + + class SubtractAction extends CheckedIntegerAction + implements BiConsumer + { + SubtractAction(ExecutionMode m) { super(m); } + public void accept(Integer x, Integer y) { + invoked(); + value = subtract(x, y); + } + } + + class SubtractFunction extends CheckedIntegerAction + implements BiFunction + { + SubtractFunction(ExecutionMode m) { super(m); } + public Integer apply(Integer x, Integer y) { + invoked(); + return value = subtract(x, y); + } + } + + class Noop extends CheckedAction implements Runnable { + Noop(ExecutionMode m) { super(m); } + public void run() { + invoked(); + } + } + + class FailingSupplier extends CheckedAction + implements Supplier + { + FailingSupplier(ExecutionMode m) { super(m); } + public Integer get() { + invoked(); + throw new CFException(); + } + } + + class FailingConsumer extends CheckedIntegerAction + implements Consumer + { + FailingConsumer(ExecutionMode m) { super(m); } + public void accept(Integer x) { + invoked(); + value = x; + throw new CFException(); + } + } + + class FailingBiConsumer extends CheckedIntegerAction + implements BiConsumer + { + FailingBiConsumer(ExecutionMode m) { super(m); } + public void accept(Integer x, Integer y) { + invoked(); + value = subtract(x, y); + throw new CFException(); + } + } + + class FailingFunction extends CheckedIntegerAction + implements Function + { + FailingFunction(ExecutionMode m) { super(m); } + public Integer apply(Integer x) { + invoked(); + value = x; + throw new CFException(); + } + } + + class FailingBiFunction extends CheckedIntegerAction + implements BiFunction + { + FailingBiFunction(ExecutionMode m) { super(m); } + public Integer apply(Integer x, Integer y) { + invoked(); + value = subtract(x, y); + throw new CFException(); + } + } + + class FailingRunnable extends CheckedAction implements Runnable { + FailingRunnable(ExecutionMode m) { super(m); } + public void run() { + invoked(); + throw new CFException(); + } + } + + class CompletableFutureInc extends CheckedIntegerAction + implements Function> + { + CompletableFutureInc(ExecutionMode m) { super(m); } + public CompletableFuture apply(Integer x) { + invoked(); + value = x; + CompletableFuture f = new CompletableFuture<>(); + assertTrue(f.complete(inc(x))); + return f; + } + } + + class FailingCompletableFutureFunction extends CheckedIntegerAction + implements Function> + { + FailingCompletableFutureFunction(ExecutionMode m) { super(m); } + public CompletableFuture apply(Integer x) { + invoked(); + value = x; + throw new CFException(); + } + } + + // Used for explicit executor tests + static final class ThreadExecutor implements Executor { + final AtomicInteger count = new AtomicInteger(0); + static final ThreadGroup tg = new ThreadGroup("ThreadExecutor"); + static boolean startedCurrentThread() { + return Thread.currentThread().getThreadGroup() == tg; + } + + public void execute(Runnable r) { + count.getAndIncrement(); + new Thread(tg, r).start(); + } + } + + static final boolean defaultExecutorIsCommonPool + = ForkJoinPool.getCommonPoolParallelism() > 1; + + /** + * Permits the testing of parallel code for the 3 different + * execution modes without copy/pasting all the test methods. + */ + enum ExecutionMode { + SYNC { + public void checkExecutionMode() { + assertFalse(ThreadExecutor.startedCurrentThread()); + assertNull(ForkJoinTask.getPool()); + } + public CompletableFuture runAsync(Runnable a) { + throw new UnsupportedOperationException(); + } + public CompletableFuture supplyAsync(Supplier a) { + throw new UnsupportedOperationException(); + } + public CompletableFuture thenRun + (CompletableFuture f, Runnable a) { + return f.thenRun(a); + } + public CompletableFuture thenAccept + (CompletableFuture f, Consumer a) { + return f.thenAccept(a); + } + public CompletableFuture thenApply + (CompletableFuture f, Function a) { + return f.thenApply(a); + } + public CompletableFuture thenCompose + (CompletableFuture f, + Function> a) { + return f.thenCompose(a); + } + public CompletableFuture handle + (CompletableFuture f, + BiFunction a) { + return f.handle(a); + } + public CompletableFuture whenComplete + (CompletableFuture f, + BiConsumer a) { + return f.whenComplete(a); + } + public CompletableFuture runAfterBoth + (CompletableFuture f, CompletableFuture g, Runnable a) { + return f.runAfterBoth(g, a); + } + public CompletableFuture thenAcceptBoth + (CompletableFuture f, + CompletionStage g, + BiConsumer a) { + return f.thenAcceptBoth(g, a); + } + public CompletableFuture thenCombine + (CompletableFuture f, + CompletionStage g, + BiFunction a) { + return f.thenCombine(g, a); + } + public CompletableFuture runAfterEither + (CompletableFuture f, + CompletionStage g, + java.lang.Runnable a) { + return f.runAfterEither(g, a); + } + public CompletableFuture acceptEither + (CompletableFuture f, + CompletionStage g, + Consumer a) { + return f.acceptEither(g, a); + } + public CompletableFuture applyToEither + (CompletableFuture f, + CompletionStage g, + Function a) { + return f.applyToEither(g, a); + } + }, + + ASYNC { + public void checkExecutionMode() { + assertEquals(defaultExecutorIsCommonPool, + (ForkJoinPool.commonPool() == ForkJoinTask.getPool())); + } + public CompletableFuture runAsync(Runnable a) { + return CompletableFuture.runAsync(a); + } + public CompletableFuture supplyAsync(Supplier a) { + return CompletableFuture.supplyAsync(a); + } + public CompletableFuture thenRun + (CompletableFuture f, Runnable a) { + return f.thenRunAsync(a); + } + public CompletableFuture thenAccept + (CompletableFuture f, Consumer a) { + return f.thenAcceptAsync(a); + } + public CompletableFuture thenApply + (CompletableFuture f, Function a) { + return f.thenApplyAsync(a); + } + public CompletableFuture thenCompose + (CompletableFuture f, + Function> a) { + return f.thenComposeAsync(a); + } + public CompletableFuture handle + (CompletableFuture f, + BiFunction a) { + return f.handleAsync(a); + } + public CompletableFuture whenComplete + (CompletableFuture f, + BiConsumer a) { + return f.whenCompleteAsync(a); + } + public CompletableFuture runAfterBoth + (CompletableFuture f, CompletableFuture g, Runnable a) { + return f.runAfterBothAsync(g, a); + } + public CompletableFuture thenAcceptBoth + (CompletableFuture f, + CompletionStage g, + BiConsumer a) { + return f.thenAcceptBothAsync(g, a); + } + public CompletableFuture thenCombine + (CompletableFuture f, + CompletionStage g, + BiFunction a) { + return f.thenCombineAsync(g, a); + } + public CompletableFuture runAfterEither + (CompletableFuture f, + CompletionStage g, + java.lang.Runnable a) { + return f.runAfterEitherAsync(g, a); + } + public CompletableFuture acceptEither + (CompletableFuture f, + CompletionStage g, + Consumer a) { + return f.acceptEitherAsync(g, a); + } + public CompletableFuture applyToEither + (CompletableFuture f, + CompletionStage g, + Function a) { + return f.applyToEitherAsync(g, a); + } + }, + + EXECUTOR { + public void checkExecutionMode() { + assertTrue(ThreadExecutor.startedCurrentThread()); + } + public CompletableFuture runAsync(Runnable a) { + return CompletableFuture.runAsync(a, new ThreadExecutor()); + } + public CompletableFuture supplyAsync(Supplier a) { + return CompletableFuture.supplyAsync(a, new ThreadExecutor()); + } + public CompletableFuture thenRun + (CompletableFuture f, Runnable a) { + return f.thenRunAsync(a, new ThreadExecutor()); + } + public CompletableFuture thenAccept + (CompletableFuture f, Consumer a) { + return f.thenAcceptAsync(a, new ThreadExecutor()); + } + public CompletableFuture thenApply + (CompletableFuture f, Function a) { + return f.thenApplyAsync(a, new ThreadExecutor()); + } + public CompletableFuture thenCompose + (CompletableFuture f, + Function> a) { + return f.thenComposeAsync(a, new ThreadExecutor()); + } + public CompletableFuture handle + (CompletableFuture f, + BiFunction a) { + return f.handleAsync(a, new ThreadExecutor()); + } + public CompletableFuture whenComplete + (CompletableFuture f, + BiConsumer a) { + return f.whenCompleteAsync(a, new ThreadExecutor()); + } + public CompletableFuture runAfterBoth + (CompletableFuture f, CompletableFuture g, Runnable a) { + return f.runAfterBothAsync(g, a, new ThreadExecutor()); + } + public CompletableFuture thenAcceptBoth + (CompletableFuture f, + CompletionStage g, + BiConsumer a) { + return f.thenAcceptBothAsync(g, a, new ThreadExecutor()); + } + public CompletableFuture thenCombine + (CompletableFuture f, + CompletionStage g, + BiFunction a) { + return f.thenCombineAsync(g, a, new ThreadExecutor()); + } + public CompletableFuture runAfterEither + (CompletableFuture f, + CompletionStage g, + java.lang.Runnable a) { + return f.runAfterEitherAsync(g, a, new ThreadExecutor()); + } + public CompletableFuture acceptEither + (CompletableFuture f, + CompletionStage g, + Consumer a) { + return f.acceptEitherAsync(g, a, new ThreadExecutor()); + } + public CompletableFuture applyToEither + (CompletableFuture f, + CompletionStage g, + Function a) { + return f.applyToEitherAsync(g, a, new ThreadExecutor()); + } + }; + + public abstract void checkExecutionMode(); + public abstract CompletableFuture runAsync(Runnable a); + public abstract CompletableFuture supplyAsync(Supplier a); + public abstract CompletableFuture thenRun + (CompletableFuture f, Runnable a); + public abstract CompletableFuture thenAccept + (CompletableFuture f, Consumer a); + public abstract CompletableFuture thenApply + (CompletableFuture f, Function a); + public abstract CompletableFuture thenCompose + (CompletableFuture f, + Function> a); + public abstract CompletableFuture handle + (CompletableFuture f, + BiFunction a); + public abstract CompletableFuture whenComplete + (CompletableFuture f, + BiConsumer a); + public abstract CompletableFuture runAfterBoth + (CompletableFuture f, CompletableFuture g, Runnable a); + public abstract CompletableFuture thenAcceptBoth + (CompletableFuture f, + CompletionStage g, + BiConsumer a); + public abstract CompletableFuture thenCombine + (CompletableFuture f, + CompletionStage g, + BiFunction a); + public abstract CompletableFuture runAfterEither + (CompletableFuture f, + CompletionStage g, + java.lang.Runnable a); + public abstract CompletableFuture acceptEither + (CompletableFuture f, + CompletionStage g, + Consumer a); + public abstract CompletableFuture applyToEither + (CompletableFuture f, + CompletionStage g, + Function a); + } + + /** + * exceptionally action is not invoked when source completes + * normally, and source result is propagated + */ + public void testExceptionally_normalCompletion() { + for (boolean createIncomplete : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final AtomicInteger a = new AtomicInteger(0); + final CompletableFuture f = new CompletableFuture<>(); + if (!createIncomplete) assertTrue(f.complete(v1)); + final CompletableFuture g = f.exceptionally + ((Throwable t) -> { + a.getAndIncrement(); + threadFail("should not be called"); + return null; // unreached + }); + if (createIncomplete) assertTrue(f.complete(v1)); + + checkCompletedNormally(g, v1); + checkCompletedNormally(f, v1); + assertEquals(0, a.get()); + }} + + /** + * exceptionally action completes with function value on source + * exception + */ + public void testExceptionally_exceptionalCompletion() { + for (boolean createIncomplete : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final AtomicInteger a = new AtomicInteger(0); + final CFException ex = new CFException(); + final CompletableFuture f = new CompletableFuture<>(); + if (!createIncomplete) f.completeExceptionally(ex); + final CompletableFuture g = f.exceptionally + ((Throwable t) -> { + ExecutionMode.SYNC.checkExecutionMode(); + threadAssertSame(t, ex); + a.getAndIncrement(); + return v1; + }); + if (createIncomplete) f.completeExceptionally(ex); + + checkCompletedNormally(g, v1); + assertEquals(1, a.get()); + }} + + /** + * If an "exceptionally action" throws an exception, it completes + * exceptionally with that exception + */ + public void testExceptionally_exceptionalCompletionActionFailed() { + for (boolean createIncomplete : new boolean[] { true, false }) + { + final AtomicInteger a = new AtomicInteger(0); + final CFException ex1 = new CFException(); + final CFException ex2 = new CFException(); + final CompletableFuture f = new CompletableFuture<>(); + if (!createIncomplete) f.completeExceptionally(ex1); + final CompletableFuture g = f.exceptionally + ((Throwable t) -> { + ExecutionMode.SYNC.checkExecutionMode(); + threadAssertSame(t, ex1); + a.getAndIncrement(); + throw ex2; + }); + if (createIncomplete) f.completeExceptionally(ex1); + + checkCompletedWithWrappedException(g, ex2); + checkCompletedExceptionally(f, ex1); + assertEquals(1, a.get()); + }} + + /** + * whenComplete action executes on normal completion, propagating + * source result. + */ + public void testWhenComplete_normalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean createIncomplete : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final AtomicInteger a = new AtomicInteger(0); + final CompletableFuture f = new CompletableFuture<>(); + if (!createIncomplete) assertTrue(f.complete(v1)); + final CompletableFuture g = m.whenComplete + (f, + (Integer result, Throwable t) -> { + m.checkExecutionMode(); + threadAssertSame(result, v1); + threadAssertNull(t); + a.getAndIncrement(); + }); + if (createIncomplete) assertTrue(f.complete(v1)); + + checkCompletedNormally(g, v1); + checkCompletedNormally(f, v1); + assertEquals(1, a.get()); + }} + + /** + * whenComplete action executes on exceptional completion, propagating + * source result. + */ + public void testWhenComplete_exceptionalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean createIncomplete : new boolean[] { true, false }) + { + final AtomicInteger a = new AtomicInteger(0); + final CFException ex = new CFException(); + final CompletableFuture f = new CompletableFuture<>(); + if (!createIncomplete) f.completeExceptionally(ex); + final CompletableFuture g = m.whenComplete + (f, + (Integer result, Throwable t) -> { + m.checkExecutionMode(); + threadAssertNull(result); + threadAssertSame(t, ex); + a.getAndIncrement(); + }); + if (createIncomplete) f.completeExceptionally(ex); + + checkCompletedWithWrappedException(g, ex); + checkCompletedExceptionally(f, ex); + assertEquals(1, a.get()); + }} + + /** + * whenComplete action executes on cancelled source, propagating + * CancellationException. + */ + public void testWhenComplete_sourceCancelled() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + for (boolean createIncomplete : new boolean[] { true, false }) + { + final AtomicInteger a = new AtomicInteger(0); + final CompletableFuture f = new CompletableFuture<>(); + if (!createIncomplete) assertTrue(f.cancel(mayInterruptIfRunning)); + final CompletableFuture g = m.whenComplete + (f, + (Integer result, Throwable t) -> { + m.checkExecutionMode(); + threadAssertNull(result); + threadAssertTrue(t instanceof CancellationException); + a.getAndIncrement(); + }); + if (createIncomplete) assertTrue(f.cancel(mayInterruptIfRunning)); + + checkCompletedWithWrappedCancellationException(g); + checkCancelled(f); + assertEquals(1, a.get()); + }} + + /** + * If a whenComplete action throws an exception when triggered by + * a normal completion, it completes exceptionally + */ + public void testWhenComplete_sourceCompletedNormallyActionFailed() { + for (boolean createIncomplete : new boolean[] { true, false }) + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + { + final AtomicInteger a = new AtomicInteger(0); + final CFException ex = new CFException(); + final CompletableFuture f = new CompletableFuture<>(); + if (!createIncomplete) assertTrue(f.complete(v1)); + final CompletableFuture g = m.whenComplete + (f, + (Integer result, Throwable t) -> { + m.checkExecutionMode(); + threadAssertSame(result, v1); + threadAssertNull(t); + a.getAndIncrement(); + throw ex; + }); + if (createIncomplete) assertTrue(f.complete(v1)); + + checkCompletedWithWrappedException(g, ex); + checkCompletedNormally(f, v1); + assertEquals(1, a.get()); + }} + + /** + * If a whenComplete action throws an exception when triggered by + * a source completion that also throws an exception, the source + * exception takes precedence (unlike handle) + */ + public void testWhenComplete_sourceFailedActionFailed() { + for (boolean createIncomplete : new boolean[] { true, false }) + for (ExecutionMode m : ExecutionMode.values()) + { + final AtomicInteger a = new AtomicInteger(0); + final CFException ex1 = new CFException(); + final CFException ex2 = new CFException(); + final CompletableFuture f = new CompletableFuture<>(); + + if (!createIncomplete) f.completeExceptionally(ex1); + final CompletableFuture g = m.whenComplete + (f, + (Integer result, Throwable t) -> { + m.checkExecutionMode(); + threadAssertSame(t, ex1); + threadAssertNull(result); + a.getAndIncrement(); + throw ex2; + }); + if (createIncomplete) f.completeExceptionally(ex1); + + checkCompletedWithWrappedException(g, ex1); + checkCompletedExceptionally(f, ex1); + // oops... temporarily disabled +// if (testImplementationDetails) { +// assertEquals(1, ex1.getSuppressed().length); +// assertSame(ex2, ex1.getSuppressed()[0]); +// } + assertEquals(1, a.get()); + }} + + /** + * handle action completes normally with function value on normal + * completion of source + */ + public void testHandle_normalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean createIncomplete : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final AtomicInteger a = new AtomicInteger(0); + if (!createIncomplete) assertTrue(f.complete(v1)); + final CompletableFuture g = m.handle + (f, + (Integer result, Throwable t) -> { + m.checkExecutionMode(); + threadAssertSame(result, v1); + threadAssertNull(t); + a.getAndIncrement(); + return inc(v1); + }); + if (createIncomplete) assertTrue(f.complete(v1)); + + checkCompletedNormally(g, inc(v1)); + checkCompletedNormally(f, v1); + assertEquals(1, a.get()); + }} + + /** + * handle action completes normally with function value on + * exceptional completion of source + */ + public void testHandle_exceptionalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean createIncomplete : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final AtomicInteger a = new AtomicInteger(0); + final CFException ex = new CFException(); + if (!createIncomplete) f.completeExceptionally(ex); + final CompletableFuture g = m.handle + (f, + (Integer result, Throwable t) -> { + m.checkExecutionMode(); + threadAssertNull(result); + threadAssertSame(t, ex); + a.getAndIncrement(); + return v1; + }); + if (createIncomplete) f.completeExceptionally(ex); + + checkCompletedNormally(g, v1); + checkCompletedExceptionally(f, ex); + assertEquals(1, a.get()); + }} + + /** + * handle action completes normally with function value on + * cancelled source + */ + public void testHandle_sourceCancelled() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + for (boolean createIncomplete : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final AtomicInteger a = new AtomicInteger(0); + if (!createIncomplete) assertTrue(f.cancel(mayInterruptIfRunning)); + final CompletableFuture g = m.handle + (f, + (Integer result, Throwable t) -> { + m.checkExecutionMode(); + threadAssertNull(result); + threadAssertTrue(t instanceof CancellationException); + a.getAndIncrement(); + return v1; + }); + if (createIncomplete) assertTrue(f.cancel(mayInterruptIfRunning)); + + checkCompletedNormally(g, v1); + checkCancelled(f); + assertEquals(1, a.get()); + }} + + /** + * If a "handle action" throws an exception when triggered by + * a normal completion, it completes exceptionally + */ + public void testHandle_sourceCompletedNormallyActionFailed() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean createIncomplete : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final AtomicInteger a = new AtomicInteger(0); + final CFException ex = new CFException(); + if (!createIncomplete) assertTrue(f.complete(v1)); + final CompletableFuture g = m.handle + (f, + (Integer result, Throwable t) -> { + m.checkExecutionMode(); + threadAssertSame(result, v1); + threadAssertNull(t); + a.getAndIncrement(); + throw ex; + }); + if (createIncomplete) assertTrue(f.complete(v1)); + + checkCompletedWithWrappedException(g, ex); + checkCompletedNormally(f, v1); + assertEquals(1, a.get()); + }} + + /** + * If a "handle action" throws an exception when triggered by + * a source completion that also throws an exception, the action + * exception takes precedence (unlike whenComplete) + */ + public void testHandle_sourceFailedActionFailed() { + for (boolean createIncomplete : new boolean[] { true, false }) + for (ExecutionMode m : ExecutionMode.values()) + { + final AtomicInteger a = new AtomicInteger(0); + final CFException ex1 = new CFException(); + final CFException ex2 = new CFException(); + final CompletableFuture f = new CompletableFuture<>(); + + if (!createIncomplete) f.completeExceptionally(ex1); + final CompletableFuture g = m.handle + (f, + (Integer result, Throwable t) -> { + m.checkExecutionMode(); + threadAssertNull(result); + threadAssertSame(ex1, t); + a.getAndIncrement(); + throw ex2; + }); + if (createIncomplete) f.completeExceptionally(ex1); + + checkCompletedWithWrappedException(g, ex2); + checkCompletedExceptionally(f, ex1); + assertEquals(1, a.get()); + }} + + /** + * runAsync completes after running Runnable + */ + public void testRunAsync_normalCompletion() { + ExecutionMode[] executionModes = { + ExecutionMode.ASYNC, + ExecutionMode.EXECUTOR, + }; + for (ExecutionMode m : executionModes) + { + final Noop r = new Noop(m); + final CompletableFuture f = m.runAsync(r); + assertNull(f.join()); + checkCompletedNormally(f, null); + r.assertInvoked(); + }} + + /** + * failing runAsync completes exceptionally after running Runnable + */ + public void testRunAsync_exceptionalCompletion() { + ExecutionMode[] executionModes = { + ExecutionMode.ASYNC, + ExecutionMode.EXECUTOR, + }; + for (ExecutionMode m : executionModes) + { + final FailingRunnable r = new FailingRunnable(m); + final CompletableFuture f = m.runAsync(r); + checkCompletedWithWrappedCFException(f); + r.assertInvoked(); + }} + + /** + * supplyAsync completes with result of supplier + */ + public void testSupplyAsync_normalCompletion() { + ExecutionMode[] executionModes = { + ExecutionMode.ASYNC, + ExecutionMode.EXECUTOR, + }; + for (ExecutionMode m : executionModes) + for (Integer v1 : new Integer[] { 1, null }) + { + final IntegerSupplier r = new IntegerSupplier(m, v1); + final CompletableFuture f = m.supplyAsync(r); + assertSame(v1, f.join()); + checkCompletedNormally(f, v1); + r.assertInvoked(); + }} + + /** + * Failing supplyAsync completes exceptionally + */ + public void testSupplyAsync_exceptionalCompletion() { + ExecutionMode[] executionModes = { + ExecutionMode.ASYNC, + ExecutionMode.EXECUTOR, + }; + for (ExecutionMode m : executionModes) + { + FailingSupplier r = new FailingSupplier(m); + CompletableFuture f = m.supplyAsync(r); + checkCompletedWithWrappedCFException(f); + r.assertInvoked(); + }} + + // seq completion methods + + /** + * thenRun result completes normally after normal completion of source + */ + public void testThenRun_normalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final Noop[] rs = new Noop[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new Noop(m); + + final CompletableFuture h0 = m.thenRun(f, rs[0]); + final CompletableFuture h1 = m.runAfterBoth(f, f, rs[1]); + final CompletableFuture h2 = m.runAfterEither(f, f, rs[2]); + checkIncomplete(h0); + checkIncomplete(h1); + checkIncomplete(h2); + assertTrue(f.complete(v1)); + final CompletableFuture h3 = m.thenRun(f, rs[3]); + final CompletableFuture h4 = m.runAfterBoth(f, f, rs[4]); + final CompletableFuture h5 = m.runAfterEither(f, f, rs[5]); + + checkCompletedNormally(h0, null); + checkCompletedNormally(h1, null); + checkCompletedNormally(h2, null); + checkCompletedNormally(h3, null); + checkCompletedNormally(h4, null); + checkCompletedNormally(h5, null); + checkCompletedNormally(f, v1); + for (Noop r : rs) r.assertInvoked(); + }} + + /** + * thenRun result completes exceptionally after exceptional + * completion of source + */ + public void testThenRun_exceptionalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + { + final CFException ex = new CFException(); + final CompletableFuture f = new CompletableFuture<>(); + final Noop[] rs = new Noop[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new Noop(m); + + final CompletableFuture h0 = m.thenRun(f, rs[0]); + final CompletableFuture h1 = m.runAfterBoth(f, f, rs[1]); + final CompletableFuture h2 = m.runAfterEither(f, f, rs[2]); + checkIncomplete(h0); + checkIncomplete(h1); + checkIncomplete(h2); + assertTrue(f.completeExceptionally(ex)); + final CompletableFuture h3 = m.thenRun(f, rs[3]); + final CompletableFuture h4 = m.runAfterBoth(f, f, rs[4]); + final CompletableFuture h5 = m.runAfterEither(f, f, rs[5]); + + checkCompletedWithWrappedException(h0, ex); + checkCompletedWithWrappedException(h1, ex); + checkCompletedWithWrappedException(h2, ex); + checkCompletedWithWrappedException(h3, ex); + checkCompletedWithWrappedException(h4, ex); + checkCompletedWithWrappedException(h5, ex); + checkCompletedExceptionally(f, ex); + for (Noop r : rs) r.assertNotInvoked(); + }} + + /** + * thenRun result completes exceptionally if source cancelled + */ + public void testThenRun_sourceCancelled() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + { + final CompletableFuture f = new CompletableFuture<>(); + final Noop[] rs = new Noop[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new Noop(m); + + final CompletableFuture h0 = m.thenRun(f, rs[0]); + final CompletableFuture h1 = m.runAfterBoth(f, f, rs[1]); + final CompletableFuture h2 = m.runAfterEither(f, f, rs[2]); + checkIncomplete(h0); + checkIncomplete(h1); + checkIncomplete(h2); + assertTrue(f.cancel(mayInterruptIfRunning)); + final CompletableFuture h3 = m.thenRun(f, rs[3]); + final CompletableFuture h4 = m.runAfterBoth(f, f, rs[4]); + final CompletableFuture h5 = m.runAfterEither(f, f, rs[5]); + + checkCompletedWithWrappedCancellationException(h0); + checkCompletedWithWrappedCancellationException(h1); + checkCompletedWithWrappedCancellationException(h2); + checkCompletedWithWrappedCancellationException(h3); + checkCompletedWithWrappedCancellationException(h4); + checkCompletedWithWrappedCancellationException(h5); + checkCancelled(f); + for (Noop r : rs) r.assertNotInvoked(); + }} + + /** + * thenRun result completes exceptionally if action does + */ + public void testThenRun_actionFailed() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final FailingRunnable[] rs = new FailingRunnable[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new FailingRunnable(m); + + final CompletableFuture h0 = m.thenRun(f, rs[0]); + final CompletableFuture h1 = m.runAfterBoth(f, f, rs[1]); + final CompletableFuture h2 = m.runAfterEither(f, f, rs[2]); + assertTrue(f.complete(v1)); + final CompletableFuture h3 = m.thenRun(f, rs[3]); + final CompletableFuture h4 = m.runAfterBoth(f, f, rs[4]); + final CompletableFuture h5 = m.runAfterEither(f, f, rs[5]); + + checkCompletedWithWrappedCFException(h0); + checkCompletedWithWrappedCFException(h1); + checkCompletedWithWrappedCFException(h2); + checkCompletedWithWrappedCFException(h3); + checkCompletedWithWrappedCFException(h4); + checkCompletedWithWrappedCFException(h5); + checkCompletedNormally(f, v1); + }} + + /** + * thenApply result completes normally after normal completion of source + */ + public void testThenApply_normalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final IncFunction[] rs = new IncFunction[4]; + for (int i = 0; i < rs.length; i++) rs[i] = new IncFunction(m); + + final CompletableFuture h0 = m.thenApply(f, rs[0]); + final CompletableFuture h1 = m.applyToEither(f, f, rs[1]); + checkIncomplete(h0); + checkIncomplete(h1); + assertTrue(f.complete(v1)); + final CompletableFuture h2 = m.thenApply(f, rs[2]); + final CompletableFuture h3 = m.applyToEither(f, f, rs[3]); + + checkCompletedNormally(h0, inc(v1)); + checkCompletedNormally(h1, inc(v1)); + checkCompletedNormally(h2, inc(v1)); + checkCompletedNormally(h3, inc(v1)); + checkCompletedNormally(f, v1); + for (IncFunction r : rs) r.assertValue(inc(v1)); + }} + + /** + * thenApply result completes exceptionally after exceptional + * completion of source + */ + public void testThenApply_exceptionalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + { + final CFException ex = new CFException(); + final CompletableFuture f = new CompletableFuture<>(); + final IncFunction[] rs = new IncFunction[4]; + for (int i = 0; i < rs.length; i++) rs[i] = new IncFunction(m); + + final CompletableFuture h0 = m.thenApply(f, rs[0]); + final CompletableFuture h1 = m.applyToEither(f, f, rs[1]); + assertTrue(f.completeExceptionally(ex)); + final CompletableFuture h2 = m.thenApply(f, rs[2]); + final CompletableFuture h3 = m.applyToEither(f, f, rs[3]); + + checkCompletedWithWrappedException(h0, ex); + checkCompletedWithWrappedException(h1, ex); + checkCompletedWithWrappedException(h2, ex); + checkCompletedWithWrappedException(h3, ex); + checkCompletedExceptionally(f, ex); + for (IncFunction r : rs) r.assertNotInvoked(); + }} + + /** + * thenApply result completes exceptionally if source cancelled + */ + public void testThenApply_sourceCancelled() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + { + final CompletableFuture f = new CompletableFuture<>(); + final IncFunction[] rs = new IncFunction[4]; + for (int i = 0; i < rs.length; i++) rs[i] = new IncFunction(m); + + final CompletableFuture h0 = m.thenApply(f, rs[0]); + final CompletableFuture h1 = m.applyToEither(f, f, rs[1]); + assertTrue(f.cancel(mayInterruptIfRunning)); + final CompletableFuture h2 = m.thenApply(f, rs[2]); + final CompletableFuture h3 = m.applyToEither(f, f, rs[3]); + + checkCompletedWithWrappedCancellationException(h0); + checkCompletedWithWrappedCancellationException(h1); + checkCompletedWithWrappedCancellationException(h2); + checkCompletedWithWrappedCancellationException(h3); + checkCancelled(f); + for (IncFunction r : rs) r.assertNotInvoked(); + }} + + /** + * thenApply result completes exceptionally if action does + */ + public void testThenApply_actionFailed() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final FailingFunction[] rs = new FailingFunction[4]; + for (int i = 0; i < rs.length; i++) rs[i] = new FailingFunction(m); + + final CompletableFuture h0 = m.thenApply(f, rs[0]); + final CompletableFuture h1 = m.applyToEither(f, f, rs[1]); + assertTrue(f.complete(v1)); + final CompletableFuture h2 = m.thenApply(f, rs[2]); + final CompletableFuture h3 = m.applyToEither(f, f, rs[3]); + + checkCompletedWithWrappedCFException(h0); + checkCompletedWithWrappedCFException(h1); + checkCompletedWithWrappedCFException(h2); + checkCompletedWithWrappedCFException(h3); + checkCompletedNormally(f, v1); + }} + + /** + * thenAccept result completes normally after normal completion of source + */ + public void testThenAccept_normalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final NoopConsumer[] rs = new NoopConsumer[4]; + for (int i = 0; i < rs.length; i++) rs[i] = new NoopConsumer(m); + + final CompletableFuture h0 = m.thenAccept(f, rs[0]); + final CompletableFuture h1 = m.acceptEither(f, f, rs[1]); + checkIncomplete(h0); + checkIncomplete(h1); + assertTrue(f.complete(v1)); + final CompletableFuture h2 = m.thenAccept(f, rs[2]); + final CompletableFuture h3 = m.acceptEither(f, f, rs[3]); + + checkCompletedNormally(h0, null); + checkCompletedNormally(h1, null); + checkCompletedNormally(h2, null); + checkCompletedNormally(h3, null); + checkCompletedNormally(f, v1); + for (NoopConsumer r : rs) r.assertValue(v1); + }} + + /** + * thenAccept result completes exceptionally after exceptional + * completion of source + */ + public void testThenAccept_exceptionalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + { + final CFException ex = new CFException(); + final CompletableFuture f = new CompletableFuture<>(); + final NoopConsumer[] rs = new NoopConsumer[4]; + for (int i = 0; i < rs.length; i++) rs[i] = new NoopConsumer(m); + + final CompletableFuture h0 = m.thenAccept(f, rs[0]); + final CompletableFuture h1 = m.acceptEither(f, f, rs[1]); + assertTrue(f.completeExceptionally(ex)); + final CompletableFuture h2 = m.thenAccept(f, rs[2]); + final CompletableFuture h3 = m.acceptEither(f, f, rs[3]); + + checkCompletedWithWrappedException(h0, ex); + checkCompletedWithWrappedException(h1, ex); + checkCompletedWithWrappedException(h2, ex); + checkCompletedWithWrappedException(h3, ex); + checkCompletedExceptionally(f, ex); + for (NoopConsumer r : rs) r.assertNotInvoked(); + }} + + /** + * thenAccept result completes exceptionally if source cancelled + */ + public void testThenAccept_sourceCancelled() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + { + final CompletableFuture f = new CompletableFuture<>(); + final NoopConsumer[] rs = new NoopConsumer[4]; + for (int i = 0; i < rs.length; i++) rs[i] = new NoopConsumer(m); + + final CompletableFuture h0 = m.thenAccept(f, rs[0]); + final CompletableFuture h1 = m.acceptEither(f, f, rs[1]); + assertTrue(f.cancel(mayInterruptIfRunning)); + final CompletableFuture h2 = m.thenAccept(f, rs[2]); + final CompletableFuture h3 = m.acceptEither(f, f, rs[3]); + + checkCompletedWithWrappedCancellationException(h0); + checkCompletedWithWrappedCancellationException(h1); + checkCompletedWithWrappedCancellationException(h2); + checkCompletedWithWrappedCancellationException(h3); + checkCancelled(f); + for (NoopConsumer r : rs) r.assertNotInvoked(); + }} + + /** + * thenAccept result completes exceptionally if action does + */ + public void testThenAccept_actionFailed() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final FailingConsumer[] rs = new FailingConsumer[4]; + for (int i = 0; i < rs.length; i++) rs[i] = new FailingConsumer(m); + + final CompletableFuture h0 = m.thenAccept(f, rs[0]); + final CompletableFuture h1 = m.acceptEither(f, f, rs[1]); + assertTrue(f.complete(v1)); + final CompletableFuture h2 = m.thenAccept(f, rs[2]); + final CompletableFuture h3 = m.acceptEither(f, f, rs[3]); + + checkCompletedWithWrappedCFException(h0); + checkCompletedWithWrappedCFException(h1); + checkCompletedWithWrappedCFException(h2); + checkCompletedWithWrappedCFException(h3); + checkCompletedNormally(f, v1); + }} + + /** + * thenCombine result completes normally after normal completion + * of sources + */ + public void testThenCombine_normalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean fFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + for (Integer v2 : new Integer[] { 2, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final SubtractFunction[] rs = new SubtractFunction[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new SubtractFunction(m); + + final CompletableFuture fst = fFirst ? f : g; + final CompletableFuture snd = !fFirst ? f : g; + final Integer w1 = fFirst ? v1 : v2; + final Integer w2 = !fFirst ? v1 : v2; + + final CompletableFuture h0 = m.thenCombine(f, g, rs[0]); + final CompletableFuture h1 = m.thenCombine(fst, fst, rs[1]); + assertTrue(fst.complete(w1)); + final CompletableFuture h2 = m.thenCombine(f, g, rs[2]); + final CompletableFuture h3 = m.thenCombine(fst, fst, rs[3]); + checkIncomplete(h0); rs[0].assertNotInvoked(); + checkIncomplete(h2); rs[2].assertNotInvoked(); + checkCompletedNormally(h1, subtract(w1, w1)); + checkCompletedNormally(h3, subtract(w1, w1)); + rs[1].assertValue(subtract(w1, w1)); + rs[3].assertValue(subtract(w1, w1)); + assertTrue(snd.complete(w2)); + final CompletableFuture h4 = m.thenCombine(f, g, rs[4]); + + checkCompletedNormally(h0, subtract(v1, v2)); + checkCompletedNormally(h2, subtract(v1, v2)); + checkCompletedNormally(h4, subtract(v1, v2)); + rs[0].assertValue(subtract(v1, v2)); + rs[2].assertValue(subtract(v1, v2)); + rs[4].assertValue(subtract(v1, v2)); + + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v2); + }} + + /** + * thenCombine result completes exceptionally after exceptional + * completion of either source + */ + public void testThenCombine_exceptionalCompletion() throws Throwable { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean fFirst : new boolean[] { true, false }) + for (boolean failFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final CFException ex = new CFException(); + final SubtractFunction r1 = new SubtractFunction(m); + final SubtractFunction r2 = new SubtractFunction(m); + final SubtractFunction r3 = new SubtractFunction(m); + + final CompletableFuture fst = fFirst ? f : g; + final CompletableFuture snd = !fFirst ? f : g; + final Callable complete1 = failFirst ? + () -> fst.completeExceptionally(ex) : + () -> fst.complete(v1); + final Callable complete2 = failFirst ? + () -> snd.complete(v1) : + () -> snd.completeExceptionally(ex); + + final CompletableFuture h1 = m.thenCombine(f, g, r1); + assertTrue(complete1.call()); + final CompletableFuture h2 = m.thenCombine(f, g, r2); + checkIncomplete(h1); + checkIncomplete(h2); + assertTrue(complete2.call()); + final CompletableFuture h3 = m.thenCombine(f, g, r3); + + checkCompletedWithWrappedException(h1, ex); + checkCompletedWithWrappedException(h2, ex); + checkCompletedWithWrappedException(h3, ex); + r1.assertNotInvoked(); + r2.assertNotInvoked(); + r3.assertNotInvoked(); + checkCompletedNormally(failFirst ? snd : fst, v1); + checkCompletedExceptionally(failFirst ? fst : snd, ex); + }} + + /** + * thenCombine result completes exceptionally if either source cancelled + */ + public void testThenCombine_sourceCancelled() throws Throwable { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + for (boolean fFirst : new boolean[] { true, false }) + for (boolean failFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final SubtractFunction r1 = new SubtractFunction(m); + final SubtractFunction r2 = new SubtractFunction(m); + final SubtractFunction r3 = new SubtractFunction(m); + + final CompletableFuture fst = fFirst ? f : g; + final CompletableFuture snd = !fFirst ? f : g; + final Callable complete1 = failFirst ? + () -> fst.cancel(mayInterruptIfRunning) : + () -> fst.complete(v1); + final Callable complete2 = failFirst ? + () -> snd.complete(v1) : + () -> snd.cancel(mayInterruptIfRunning); + + final CompletableFuture h1 = m.thenCombine(f, g, r1); + assertTrue(complete1.call()); + final CompletableFuture h2 = m.thenCombine(f, g, r2); + checkIncomplete(h1); + checkIncomplete(h2); + assertTrue(complete2.call()); + final CompletableFuture h3 = m.thenCombine(f, g, r3); + + checkCompletedWithWrappedCancellationException(h1); + checkCompletedWithWrappedCancellationException(h2); + checkCompletedWithWrappedCancellationException(h3); + r1.assertNotInvoked(); + r2.assertNotInvoked(); + r3.assertNotInvoked(); + checkCompletedNormally(failFirst ? snd : fst, v1); + checkCancelled(failFirst ? fst : snd); + }} + + /** + * thenCombine result completes exceptionally if action does + */ + public void testThenCombine_actionFailed() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean fFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + for (Integer v2 : new Integer[] { 2, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final FailingBiFunction r1 = new FailingBiFunction(m); + final FailingBiFunction r2 = new FailingBiFunction(m); + final FailingBiFunction r3 = new FailingBiFunction(m); + + final CompletableFuture fst = fFirst ? f : g; + final CompletableFuture snd = !fFirst ? f : g; + final Integer w1 = fFirst ? v1 : v2; + final Integer w2 = !fFirst ? v1 : v2; + + final CompletableFuture h1 = m.thenCombine(f, g, r1); + assertTrue(fst.complete(w1)); + final CompletableFuture h2 = m.thenCombine(f, g, r2); + assertTrue(snd.complete(w2)); + final CompletableFuture h3 = m.thenCombine(f, g, r3); + + checkCompletedWithWrappedCFException(h1); + checkCompletedWithWrappedCFException(h2); + checkCompletedWithWrappedCFException(h3); + r1.assertInvoked(); + r2.assertInvoked(); + r3.assertInvoked(); + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v2); + }} + + /** + * thenAcceptBoth result completes normally after normal + * completion of sources + */ + public void testThenAcceptBoth_normalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean fFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + for (Integer v2 : new Integer[] { 2, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final SubtractAction r1 = new SubtractAction(m); + final SubtractAction r2 = new SubtractAction(m); + final SubtractAction r3 = new SubtractAction(m); + + final CompletableFuture fst = fFirst ? f : g; + final CompletableFuture snd = !fFirst ? f : g; + final Integer w1 = fFirst ? v1 : v2; + final Integer w2 = !fFirst ? v1 : v2; + + final CompletableFuture h1 = m.thenAcceptBoth(f, g, r1); + assertTrue(fst.complete(w1)); + final CompletableFuture h2 = m.thenAcceptBoth(f, g, r2); + checkIncomplete(h1); + checkIncomplete(h2); + r1.assertNotInvoked(); + r2.assertNotInvoked(); + assertTrue(snd.complete(w2)); + final CompletableFuture h3 = m.thenAcceptBoth(f, g, r3); + + checkCompletedNormally(h1, null); + checkCompletedNormally(h2, null); + checkCompletedNormally(h3, null); + r1.assertValue(subtract(v1, v2)); + r2.assertValue(subtract(v1, v2)); + r3.assertValue(subtract(v1, v2)); + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v2); + }} + + /** + * thenAcceptBoth result completes exceptionally after exceptional + * completion of either source + */ + public void testThenAcceptBoth_exceptionalCompletion() throws Throwable { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean fFirst : new boolean[] { true, false }) + for (boolean failFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final CFException ex = new CFException(); + final SubtractAction r1 = new SubtractAction(m); + final SubtractAction r2 = new SubtractAction(m); + final SubtractAction r3 = new SubtractAction(m); + + final CompletableFuture fst = fFirst ? f : g; + final CompletableFuture snd = !fFirst ? f : g; + final Callable complete1 = failFirst ? + () -> fst.completeExceptionally(ex) : + () -> fst.complete(v1); + final Callable complete2 = failFirst ? + () -> snd.complete(v1) : + () -> snd.completeExceptionally(ex); + + final CompletableFuture h1 = m.thenAcceptBoth(f, g, r1); + assertTrue(complete1.call()); + final CompletableFuture h2 = m.thenAcceptBoth(f, g, r2); + checkIncomplete(h1); + checkIncomplete(h2); + assertTrue(complete2.call()); + final CompletableFuture h3 = m.thenAcceptBoth(f, g, r3); + + checkCompletedWithWrappedException(h1, ex); + checkCompletedWithWrappedException(h2, ex); + checkCompletedWithWrappedException(h3, ex); + r1.assertNotInvoked(); + r2.assertNotInvoked(); + r3.assertNotInvoked(); + checkCompletedNormally(failFirst ? snd : fst, v1); + checkCompletedExceptionally(failFirst ? fst : snd, ex); + }} + + /** + * thenAcceptBoth result completes exceptionally if either source cancelled + */ + public void testThenAcceptBoth_sourceCancelled() throws Throwable { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + for (boolean fFirst : new boolean[] { true, false }) + for (boolean failFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final SubtractAction r1 = new SubtractAction(m); + final SubtractAction r2 = new SubtractAction(m); + final SubtractAction r3 = new SubtractAction(m); + + final CompletableFuture fst = fFirst ? f : g; + final CompletableFuture snd = !fFirst ? f : g; + final Callable complete1 = failFirst ? + () -> fst.cancel(mayInterruptIfRunning) : + () -> fst.complete(v1); + final Callable complete2 = failFirst ? + () -> snd.complete(v1) : + () -> snd.cancel(mayInterruptIfRunning); + + final CompletableFuture h1 = m.thenAcceptBoth(f, g, r1); + assertTrue(complete1.call()); + final CompletableFuture h2 = m.thenAcceptBoth(f, g, r2); + checkIncomplete(h1); + checkIncomplete(h2); + assertTrue(complete2.call()); + final CompletableFuture h3 = m.thenAcceptBoth(f, g, r3); + + checkCompletedWithWrappedCancellationException(h1); + checkCompletedWithWrappedCancellationException(h2); + checkCompletedWithWrappedCancellationException(h3); + r1.assertNotInvoked(); + r2.assertNotInvoked(); + r3.assertNotInvoked(); + checkCompletedNormally(failFirst ? snd : fst, v1); + checkCancelled(failFirst ? fst : snd); + }} + + /** + * thenAcceptBoth result completes exceptionally if action does + */ + public void testThenAcceptBoth_actionFailed() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean fFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + for (Integer v2 : new Integer[] { 2, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final FailingBiConsumer r1 = new FailingBiConsumer(m); + final FailingBiConsumer r2 = new FailingBiConsumer(m); + final FailingBiConsumer r3 = new FailingBiConsumer(m); + + final CompletableFuture fst = fFirst ? f : g; + final CompletableFuture snd = !fFirst ? f : g; + final Integer w1 = fFirst ? v1 : v2; + final Integer w2 = !fFirst ? v1 : v2; + + final CompletableFuture h1 = m.thenAcceptBoth(f, g, r1); + assertTrue(fst.complete(w1)); + final CompletableFuture h2 = m.thenAcceptBoth(f, g, r2); + assertTrue(snd.complete(w2)); + final CompletableFuture h3 = m.thenAcceptBoth(f, g, r3); + + checkCompletedWithWrappedCFException(h1); + checkCompletedWithWrappedCFException(h2); + checkCompletedWithWrappedCFException(h3); + r1.assertInvoked(); + r2.assertInvoked(); + r3.assertInvoked(); + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v2); + }} + + /** + * runAfterBoth result completes normally after normal + * completion of sources + */ + public void testRunAfterBoth_normalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean fFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + for (Integer v2 : new Integer[] { 2, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final Noop r1 = new Noop(m); + final Noop r2 = new Noop(m); + final Noop r3 = new Noop(m); + + final CompletableFuture fst = fFirst ? f : g; + final CompletableFuture snd = !fFirst ? f : g; + final Integer w1 = fFirst ? v1 : v2; + final Integer w2 = !fFirst ? v1 : v2; + + final CompletableFuture h1 = m.runAfterBoth(f, g, r1); + assertTrue(fst.complete(w1)); + final CompletableFuture h2 = m.runAfterBoth(f, g, r2); + checkIncomplete(h1); + checkIncomplete(h2); + r1.assertNotInvoked(); + r2.assertNotInvoked(); + assertTrue(snd.complete(w2)); + final CompletableFuture h3 = m.runAfterBoth(f, g, r3); + + checkCompletedNormally(h1, null); + checkCompletedNormally(h2, null); + checkCompletedNormally(h3, null); + r1.assertInvoked(); + r2.assertInvoked(); + r3.assertInvoked(); + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v2); + }} + + /** + * runAfterBoth result completes exceptionally after exceptional + * completion of either source + */ + public void testRunAfterBoth_exceptionalCompletion() throws Throwable { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean fFirst : new boolean[] { true, false }) + for (boolean failFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final CFException ex = new CFException(); + final Noop r1 = new Noop(m); + final Noop r2 = new Noop(m); + final Noop r3 = new Noop(m); + + final CompletableFuture fst = fFirst ? f : g; + final CompletableFuture snd = !fFirst ? f : g; + final Callable complete1 = failFirst ? + () -> fst.completeExceptionally(ex) : + () -> fst.complete(v1); + final Callable complete2 = failFirst ? + () -> snd.complete(v1) : + () -> snd.completeExceptionally(ex); + + final CompletableFuture h1 = m.runAfterBoth(f, g, r1); + assertTrue(complete1.call()); + final CompletableFuture h2 = m.runAfterBoth(f, g, r2); + checkIncomplete(h1); + checkIncomplete(h2); + assertTrue(complete2.call()); + final CompletableFuture h3 = m.runAfterBoth(f, g, r3); + + checkCompletedWithWrappedException(h1, ex); + checkCompletedWithWrappedException(h2, ex); + checkCompletedWithWrappedException(h3, ex); + r1.assertNotInvoked(); + r2.assertNotInvoked(); + r3.assertNotInvoked(); + checkCompletedNormally(failFirst ? snd : fst, v1); + checkCompletedExceptionally(failFirst ? fst : snd, ex); + }} + + /** + * runAfterBoth result completes exceptionally if either source cancelled + */ + public void testRunAfterBoth_sourceCancelled() throws Throwable { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + for (boolean fFirst : new boolean[] { true, false }) + for (boolean failFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final Noop r1 = new Noop(m); + final Noop r2 = new Noop(m); + final Noop r3 = new Noop(m); + + final CompletableFuture fst = fFirst ? f : g; + final CompletableFuture snd = !fFirst ? f : g; + final Callable complete1 = failFirst ? + () -> fst.cancel(mayInterruptIfRunning) : + () -> fst.complete(v1); + final Callable complete2 = failFirst ? + () -> snd.complete(v1) : + () -> snd.cancel(mayInterruptIfRunning); + + final CompletableFuture h1 = m.runAfterBoth(f, g, r1); + assertTrue(complete1.call()); + final CompletableFuture h2 = m.runAfterBoth(f, g, r2); + checkIncomplete(h1); + checkIncomplete(h2); + assertTrue(complete2.call()); + final CompletableFuture h3 = m.runAfterBoth(f, g, r3); + + checkCompletedWithWrappedCancellationException(h1); + checkCompletedWithWrappedCancellationException(h2); + checkCompletedWithWrappedCancellationException(h3); + r1.assertNotInvoked(); + r2.assertNotInvoked(); + r3.assertNotInvoked(); + checkCompletedNormally(failFirst ? snd : fst, v1); + checkCancelled(failFirst ? fst : snd); + }} + + /** + * runAfterBoth result completes exceptionally if action does + */ + public void testRunAfterBoth_actionFailed() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean fFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + for (Integer v2 : new Integer[] { 2, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final FailingRunnable r1 = new FailingRunnable(m); + final FailingRunnable r2 = new FailingRunnable(m); + final FailingRunnable r3 = new FailingRunnable(m); + + final CompletableFuture fst = fFirst ? f : g; + final CompletableFuture snd = !fFirst ? f : g; + final Integer w1 = fFirst ? v1 : v2; + final Integer w2 = !fFirst ? v1 : v2; + + final CompletableFuture h1 = m.runAfterBoth(f, g, r1); + assertTrue(fst.complete(w1)); + final CompletableFuture h2 = m.runAfterBoth(f, g, r2); + assertTrue(snd.complete(w2)); + final CompletableFuture h3 = m.runAfterBoth(f, g, r3); + + checkCompletedWithWrappedCFException(h1); + checkCompletedWithWrappedCFException(h2); + checkCompletedWithWrappedCFException(h3); + r1.assertInvoked(); + r2.assertInvoked(); + r3.assertInvoked(); + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v2); + }} + + /** + * applyToEither result completes normally after normal completion + * of either source + */ + public void testApplyToEither_normalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + for (Integer v2 : new Integer[] { 2, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final IncFunction[] rs = new IncFunction[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new IncFunction(m); + + final CompletableFuture h0 = m.applyToEither(f, g, rs[0]); + final CompletableFuture h1 = m.applyToEither(g, f, rs[1]); + checkIncomplete(h0); + checkIncomplete(h1); + rs[0].assertNotInvoked(); + rs[1].assertNotInvoked(); + f.complete(v1); + checkCompletedNormally(h0, inc(v1)); + checkCompletedNormally(h1, inc(v1)); + final CompletableFuture h2 = m.applyToEither(f, g, rs[2]); + final CompletableFuture h3 = m.applyToEither(g, f, rs[3]); + checkCompletedNormally(h2, inc(v1)); + checkCompletedNormally(h3, inc(v1)); + g.complete(v2); + + // unspecified behavior - both source completions available + final CompletableFuture h4 = m.applyToEither(f, g, rs[4]); + final CompletableFuture h5 = m.applyToEither(g, f, rs[5]); + rs[4].assertValue(h4.join()); + rs[5].assertValue(h5.join()); + assertTrue(Objects.equals(inc(v1), h4.join()) || + Objects.equals(inc(v2), h4.join())); + assertTrue(Objects.equals(inc(v1), h5.join()) || + Objects.equals(inc(v2), h5.join())); + + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v2); + checkCompletedNormally(h0, inc(v1)); + checkCompletedNormally(h1, inc(v1)); + checkCompletedNormally(h2, inc(v1)); + checkCompletedNormally(h3, inc(v1)); + for (int i = 0; i < 4; i++) rs[i].assertValue(inc(v1)); + }} + + /** + * applyToEither result completes exceptionally after exceptional + * completion of either source + */ + public void testApplyToEither_exceptionalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final CFException ex = new CFException(); + final IncFunction[] rs = new IncFunction[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new IncFunction(m); + + final CompletableFuture h0 = m.applyToEither(f, g, rs[0]); + final CompletableFuture h1 = m.applyToEither(g, f, rs[1]); + checkIncomplete(h0); + checkIncomplete(h1); + rs[0].assertNotInvoked(); + rs[1].assertNotInvoked(); + f.completeExceptionally(ex); + checkCompletedWithWrappedException(h0, ex); + checkCompletedWithWrappedException(h1, ex); + final CompletableFuture h2 = m.applyToEither(f, g, rs[2]); + final CompletableFuture h3 = m.applyToEither(g, f, rs[3]); + checkCompletedWithWrappedException(h2, ex); + checkCompletedWithWrappedException(h3, ex); + g.complete(v1); + + // unspecified behavior - both source completions available + final CompletableFuture h4 = m.applyToEither(f, g, rs[4]); + final CompletableFuture h5 = m.applyToEither(g, f, rs[5]); + try { + assertEquals(inc(v1), h4.join()); + rs[4].assertValue(inc(v1)); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h4, ex); + rs[4].assertNotInvoked(); + } + try { + assertEquals(inc(v1), h5.join()); + rs[5].assertValue(inc(v1)); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h5, ex); + rs[5].assertNotInvoked(); + } + + checkCompletedExceptionally(f, ex); + checkCompletedNormally(g, v1); + checkCompletedWithWrappedException(h0, ex); + checkCompletedWithWrappedException(h1, ex); + checkCompletedWithWrappedException(h2, ex); + checkCompletedWithWrappedException(h3, ex); + checkCompletedWithWrappedException(h4, ex); + for (int i = 0; i < 4; i++) rs[i].assertNotInvoked(); + }} + + public void testApplyToEither_exceptionalCompletion2() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean fFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final CFException ex = new CFException(); + final IncFunction[] rs = new IncFunction[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new IncFunction(m); + + final CompletableFuture h0 = m.applyToEither(f, g, rs[0]); + final CompletableFuture h1 = m.applyToEither(g, f, rs[1]); + assertTrue(fFirst ? f.complete(v1) : g.completeExceptionally(ex)); + assertTrue(!fFirst ? f.complete(v1) : g.completeExceptionally(ex)); + final CompletableFuture h2 = m.applyToEither(f, g, rs[2]); + final CompletableFuture h3 = m.applyToEither(g, f, rs[3]); + + // unspecified behavior - both source completions available + try { + assertEquals(inc(v1), h0.join()); + rs[0].assertValue(inc(v1)); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h0, ex); + rs[0].assertNotInvoked(); + } + try { + assertEquals(inc(v1), h1.join()); + rs[1].assertValue(inc(v1)); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h1, ex); + rs[1].assertNotInvoked(); + } + try { + assertEquals(inc(v1), h2.join()); + rs[2].assertValue(inc(v1)); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h2, ex); + rs[2].assertNotInvoked(); + } + try { + assertEquals(inc(v1), h3.join()); + rs[3].assertValue(inc(v1)); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h3, ex); + rs[3].assertNotInvoked(); + } + + checkCompletedNormally(f, v1); + checkCompletedExceptionally(g, ex); + }} + + /** + * applyToEither result completes exceptionally if either source cancelled + */ + public void testApplyToEither_sourceCancelled() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final IncFunction[] rs = new IncFunction[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new IncFunction(m); + + final CompletableFuture h0 = m.applyToEither(f, g, rs[0]); + final CompletableFuture h1 = m.applyToEither(g, f, rs[1]); + checkIncomplete(h0); + checkIncomplete(h1); + rs[0].assertNotInvoked(); + rs[1].assertNotInvoked(); + f.cancel(mayInterruptIfRunning); + checkCompletedWithWrappedCancellationException(h0); + checkCompletedWithWrappedCancellationException(h1); + final CompletableFuture h2 = m.applyToEither(f, g, rs[2]); + final CompletableFuture h3 = m.applyToEither(g, f, rs[3]); + checkCompletedWithWrappedCancellationException(h2); + checkCompletedWithWrappedCancellationException(h3); + g.complete(v1); + + // unspecified behavior - both source completions available + final CompletableFuture h4 = m.applyToEither(f, g, rs[4]); + final CompletableFuture h5 = m.applyToEither(g, f, rs[5]); + try { + assertEquals(inc(v1), h4.join()); + rs[4].assertValue(inc(v1)); + } catch (CompletionException ok) { + checkCompletedWithWrappedCancellationException(h4); + rs[4].assertNotInvoked(); + } + try { + assertEquals(inc(v1), h5.join()); + rs[5].assertValue(inc(v1)); + } catch (CompletionException ok) { + checkCompletedWithWrappedCancellationException(h5); + rs[5].assertNotInvoked(); + } + + checkCancelled(f); + checkCompletedNormally(g, v1); + checkCompletedWithWrappedCancellationException(h0); + checkCompletedWithWrappedCancellationException(h1); + checkCompletedWithWrappedCancellationException(h2); + checkCompletedWithWrappedCancellationException(h3); + for (int i = 0; i < 4; i++) rs[i].assertNotInvoked(); + }} + + public void testApplyToEither_sourceCancelled2() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + for (boolean fFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final IncFunction[] rs = new IncFunction[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new IncFunction(m); + + final CompletableFuture h0 = m.applyToEither(f, g, rs[0]); + final CompletableFuture h1 = m.applyToEither(g, f, rs[1]); + assertTrue(fFirst ? f.complete(v1) : g.cancel(mayInterruptIfRunning)); + assertTrue(!fFirst ? f.complete(v1) : g.cancel(mayInterruptIfRunning)); + final CompletableFuture h2 = m.applyToEither(f, g, rs[2]); + final CompletableFuture h3 = m.applyToEither(g, f, rs[3]); + + // unspecified behavior - both source completions available + try { + assertEquals(inc(v1), h0.join()); + rs[0].assertValue(inc(v1)); + } catch (CompletionException ok) { + checkCompletedWithWrappedCancellationException(h0); + rs[0].assertNotInvoked(); + } + try { + assertEquals(inc(v1), h1.join()); + rs[1].assertValue(inc(v1)); + } catch (CompletionException ok) { + checkCompletedWithWrappedCancellationException(h1); + rs[1].assertNotInvoked(); + } + try { + assertEquals(inc(v1), h2.join()); + rs[2].assertValue(inc(v1)); + } catch (CompletionException ok) { + checkCompletedWithWrappedCancellationException(h2); + rs[2].assertNotInvoked(); + } + try { + assertEquals(inc(v1), h3.join()); + rs[3].assertValue(inc(v1)); + } catch (CompletionException ok) { + checkCompletedWithWrappedCancellationException(h3); + rs[3].assertNotInvoked(); + } + + checkCompletedNormally(f, v1); + checkCancelled(g); + }} + + /** + * applyToEither result completes exceptionally if action does + */ + public void testApplyToEither_actionFailed() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + for (Integer v2 : new Integer[] { 2, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final FailingFunction[] rs = new FailingFunction[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new FailingFunction(m); + + final CompletableFuture h0 = m.applyToEither(f, g, rs[0]); + final CompletableFuture h1 = m.applyToEither(g, f, rs[1]); + f.complete(v1); + final CompletableFuture h2 = m.applyToEither(f, g, rs[2]); + final CompletableFuture h3 = m.applyToEither(g, f, rs[3]); + checkCompletedWithWrappedCFException(h0); + checkCompletedWithWrappedCFException(h1); + checkCompletedWithWrappedCFException(h2); + checkCompletedWithWrappedCFException(h3); + for (int i = 0; i < 4; i++) rs[i].assertValue(v1); + + g.complete(v2); + + // unspecified behavior - both source completions available + final CompletableFuture h4 = m.applyToEither(f, g, rs[4]); + final CompletableFuture h5 = m.applyToEither(g, f, rs[5]); + + checkCompletedWithWrappedCFException(h4); + assertTrue(Objects.equals(v1, rs[4].value) || + Objects.equals(v2, rs[4].value)); + checkCompletedWithWrappedCFException(h5); + assertTrue(Objects.equals(v1, rs[5].value) || + Objects.equals(v2, rs[5].value)); + + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v2); + }} + + /** + * acceptEither result completes normally after normal completion + * of either source + */ + public void testAcceptEither_normalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + for (Integer v2 : new Integer[] { 2, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final NoopConsumer[] rs = new NoopConsumer[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new NoopConsumer(m); + + final CompletableFuture h0 = m.acceptEither(f, g, rs[0]); + final CompletableFuture h1 = m.acceptEither(g, f, rs[1]); + checkIncomplete(h0); + checkIncomplete(h1); + rs[0].assertNotInvoked(); + rs[1].assertNotInvoked(); + f.complete(v1); + checkCompletedNormally(h0, null); + checkCompletedNormally(h1, null); + rs[0].assertValue(v1); + rs[1].assertValue(v1); + final CompletableFuture h2 = m.acceptEither(f, g, rs[2]); + final CompletableFuture h3 = m.acceptEither(g, f, rs[3]); + checkCompletedNormally(h2, null); + checkCompletedNormally(h3, null); + rs[2].assertValue(v1); + rs[3].assertValue(v1); + g.complete(v2); + + // unspecified behavior - both source completions available + final CompletableFuture h4 = m.acceptEither(f, g, rs[4]); + final CompletableFuture h5 = m.acceptEither(g, f, rs[5]); + checkCompletedNormally(h4, null); + checkCompletedNormally(h5, null); + assertTrue(Objects.equals(v1, rs[4].value) || + Objects.equals(v2, rs[4].value)); + assertTrue(Objects.equals(v1, rs[5].value) || + Objects.equals(v2, rs[5].value)); + + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v2); + checkCompletedNormally(h0, null); + checkCompletedNormally(h1, null); + checkCompletedNormally(h2, null); + checkCompletedNormally(h3, null); + for (int i = 0; i < 4; i++) rs[i].assertValue(v1); + }} + + /** + * acceptEither result completes exceptionally after exceptional + * completion of either source + */ + public void testAcceptEither_exceptionalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final CFException ex = new CFException(); + final NoopConsumer[] rs = new NoopConsumer[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new NoopConsumer(m); + + final CompletableFuture h0 = m.acceptEither(f, g, rs[0]); + final CompletableFuture h1 = m.acceptEither(g, f, rs[1]); + checkIncomplete(h0); + checkIncomplete(h1); + rs[0].assertNotInvoked(); + rs[1].assertNotInvoked(); + f.completeExceptionally(ex); + checkCompletedWithWrappedException(h0, ex); + checkCompletedWithWrappedException(h1, ex); + final CompletableFuture h2 = m.acceptEither(f, g, rs[2]); + final CompletableFuture h3 = m.acceptEither(g, f, rs[3]); + checkCompletedWithWrappedException(h2, ex); + checkCompletedWithWrappedException(h3, ex); + + g.complete(v1); + + // unspecified behavior - both source completions available + final CompletableFuture h4 = m.acceptEither(f, g, rs[4]); + final CompletableFuture h5 = m.acceptEither(g, f, rs[5]); + try { + assertNull(h4.join()); + rs[4].assertValue(v1); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h4, ex); + rs[4].assertNotInvoked(); + } + try { + assertNull(h5.join()); + rs[5].assertValue(v1); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h5, ex); + rs[5].assertNotInvoked(); + } + + checkCompletedExceptionally(f, ex); + checkCompletedNormally(g, v1); + checkCompletedWithWrappedException(h0, ex); + checkCompletedWithWrappedException(h1, ex); + checkCompletedWithWrappedException(h2, ex); + checkCompletedWithWrappedException(h3, ex); + checkCompletedWithWrappedException(h4, ex); + for (int i = 0; i < 4; i++) rs[i].assertNotInvoked(); + }} + + public void testAcceptEither_exceptionalCompletion2() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean fFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final CFException ex = new CFException(); + final NoopConsumer[] rs = new NoopConsumer[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new NoopConsumer(m); + + final CompletableFuture h0 = m.acceptEither(f, g, rs[0]); + final CompletableFuture h1 = m.acceptEither(g, f, rs[1]); + assertTrue(fFirst ? f.complete(v1) : g.completeExceptionally(ex)); + assertTrue(!fFirst ? f.complete(v1) : g.completeExceptionally(ex)); + final CompletableFuture h2 = m.acceptEither(f, g, rs[2]); + final CompletableFuture h3 = m.acceptEither(g, f, rs[3]); + + // unspecified behavior - both source completions available + try { + assertEquals(null, h0.join()); + rs[0].assertValue(v1); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h0, ex); + rs[0].assertNotInvoked(); + } + try { + assertEquals(null, h1.join()); + rs[1].assertValue(v1); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h1, ex); + rs[1].assertNotInvoked(); + } + try { + assertEquals(null, h2.join()); + rs[2].assertValue(v1); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h2, ex); + rs[2].assertNotInvoked(); + } + try { + assertEquals(null, h3.join()); + rs[3].assertValue(v1); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h3, ex); + rs[3].assertNotInvoked(); + } + + checkCompletedNormally(f, v1); + checkCompletedExceptionally(g, ex); + }} + + /** + * acceptEither result completes exceptionally if either source cancelled + */ + public void testAcceptEither_sourceCancelled() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final NoopConsumer[] rs = new NoopConsumer[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new NoopConsumer(m); + + final CompletableFuture h0 = m.acceptEither(f, g, rs[0]); + final CompletableFuture h1 = m.acceptEither(g, f, rs[1]); + checkIncomplete(h0); + checkIncomplete(h1); + rs[0].assertNotInvoked(); + rs[1].assertNotInvoked(); + f.cancel(mayInterruptIfRunning); + checkCompletedWithWrappedCancellationException(h0); + checkCompletedWithWrappedCancellationException(h1); + final CompletableFuture h2 = m.acceptEither(f, g, rs[2]); + final CompletableFuture h3 = m.acceptEither(g, f, rs[3]); + checkCompletedWithWrappedCancellationException(h2); + checkCompletedWithWrappedCancellationException(h3); + + g.complete(v1); + + // unspecified behavior - both source completions available + final CompletableFuture h4 = m.acceptEither(f, g, rs[4]); + final CompletableFuture h5 = m.acceptEither(g, f, rs[5]); + try { + assertNull(h4.join()); + rs[4].assertValue(v1); + } catch (CompletionException ok) { + checkCompletedWithWrappedCancellationException(h4); + rs[4].assertNotInvoked(); + } + try { + assertNull(h5.join()); + rs[5].assertValue(v1); + } catch (CompletionException ok) { + checkCompletedWithWrappedCancellationException(h5); + rs[5].assertNotInvoked(); + } + + checkCancelled(f); + checkCompletedNormally(g, v1); + checkCompletedWithWrappedCancellationException(h0); + checkCompletedWithWrappedCancellationException(h1); + checkCompletedWithWrappedCancellationException(h2); + checkCompletedWithWrappedCancellationException(h3); + for (int i = 0; i < 4; i++) rs[i].assertNotInvoked(); + }} + + /** + * acceptEither result completes exceptionally if action does + */ + public void testAcceptEither_actionFailed() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + for (Integer v2 : new Integer[] { 2, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final FailingConsumer[] rs = new FailingConsumer[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new FailingConsumer(m); + + final CompletableFuture h0 = m.acceptEither(f, g, rs[0]); + final CompletableFuture h1 = m.acceptEither(g, f, rs[1]); + f.complete(v1); + final CompletableFuture h2 = m.acceptEither(f, g, rs[2]); + final CompletableFuture h3 = m.acceptEither(g, f, rs[3]); + checkCompletedWithWrappedCFException(h0); + checkCompletedWithWrappedCFException(h1); + checkCompletedWithWrappedCFException(h2); + checkCompletedWithWrappedCFException(h3); + for (int i = 0; i < 4; i++) rs[i].assertValue(v1); + + g.complete(v2); + + // unspecified behavior - both source completions available + final CompletableFuture h4 = m.acceptEither(f, g, rs[4]); + final CompletableFuture h5 = m.acceptEither(g, f, rs[5]); + + checkCompletedWithWrappedCFException(h4); + assertTrue(Objects.equals(v1, rs[4].value) || + Objects.equals(v2, rs[4].value)); + checkCompletedWithWrappedCFException(h5); + assertTrue(Objects.equals(v1, rs[5].value) || + Objects.equals(v2, rs[5].value)); + + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v2); + }} + + /** + * runAfterEither result completes normally after normal completion + * of either source + */ + public void testRunAfterEither_normalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + for (Integer v2 : new Integer[] { 2, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final Noop[] rs = new Noop[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new Noop(m); + + final CompletableFuture h0 = m.runAfterEither(f, g, rs[0]); + final CompletableFuture h1 = m.runAfterEither(g, f, rs[1]); + checkIncomplete(h0); + checkIncomplete(h1); + rs[0].assertNotInvoked(); + rs[1].assertNotInvoked(); + f.complete(v1); + checkCompletedNormally(h0, null); + checkCompletedNormally(h1, null); + rs[0].assertInvoked(); + rs[1].assertInvoked(); + final CompletableFuture h2 = m.runAfterEither(f, g, rs[2]); + final CompletableFuture h3 = m.runAfterEither(g, f, rs[3]); + checkCompletedNormally(h2, null); + checkCompletedNormally(h3, null); + rs[2].assertInvoked(); + rs[3].assertInvoked(); + + g.complete(v2); + + final CompletableFuture h4 = m.runAfterEither(f, g, rs[4]); + final CompletableFuture h5 = m.runAfterEither(g, f, rs[5]); + + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v2); + checkCompletedNormally(h0, null); + checkCompletedNormally(h1, null); + checkCompletedNormally(h2, null); + checkCompletedNormally(h3, null); + checkCompletedNormally(h4, null); + checkCompletedNormally(h5, null); + for (int i = 0; i < 6; i++) rs[i].assertInvoked(); + }} + + /** + * runAfterEither result completes exceptionally after exceptional + * completion of either source + */ + public void testRunAfterEither_exceptionalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final CFException ex = new CFException(); + final Noop[] rs = new Noop[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new Noop(m); + + final CompletableFuture h0 = m.runAfterEither(f, g, rs[0]); + final CompletableFuture h1 = m.runAfterEither(g, f, rs[1]); + checkIncomplete(h0); + checkIncomplete(h1); + rs[0].assertNotInvoked(); + rs[1].assertNotInvoked(); + assertTrue(f.completeExceptionally(ex)); + checkCompletedWithWrappedException(h0, ex); + checkCompletedWithWrappedException(h1, ex); + final CompletableFuture h2 = m.runAfterEither(f, g, rs[2]); + final CompletableFuture h3 = m.runAfterEither(g, f, rs[3]); + checkCompletedWithWrappedException(h2, ex); + checkCompletedWithWrappedException(h3, ex); + + assertTrue(g.complete(v1)); + + // unspecified behavior - both source completions available + final CompletableFuture h4 = m.runAfterEither(f, g, rs[4]); + final CompletableFuture h5 = m.runAfterEither(g, f, rs[5]); + try { + assertNull(h4.join()); + rs[4].assertInvoked(); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h4, ex); + rs[4].assertNotInvoked(); + } + try { + assertNull(h5.join()); + rs[5].assertInvoked(); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h5, ex); + rs[5].assertNotInvoked(); + } + + checkCompletedExceptionally(f, ex); + checkCompletedNormally(g, v1); + checkCompletedWithWrappedException(h0, ex); + checkCompletedWithWrappedException(h1, ex); + checkCompletedWithWrappedException(h2, ex); + checkCompletedWithWrappedException(h3, ex); + checkCompletedWithWrappedException(h4, ex); + for (int i = 0; i < 4; i++) rs[i].assertNotInvoked(); + }} + + public void testRunAfterEither_exceptionalCompletion2() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean fFirst : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final CFException ex = new CFException(); + final Noop[] rs = new Noop[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new Noop(m); + + final CompletableFuture h0 = m.runAfterEither(f, g, rs[0]); + final CompletableFuture h1 = m.runAfterEither(g, f, rs[1]); + assertTrue( fFirst ? f.complete(v1) : g.completeExceptionally(ex)); + assertTrue(!fFirst ? f.complete(v1) : g.completeExceptionally(ex)); + final CompletableFuture h2 = m.runAfterEither(f, g, rs[2]); + final CompletableFuture h3 = m.runAfterEither(g, f, rs[3]); + + // unspecified behavior - both source completions available + try { + assertEquals(null, h0.join()); + rs[0].assertInvoked(); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h0, ex); + rs[0].assertNotInvoked(); + } + try { + assertEquals(null, h1.join()); + rs[1].assertInvoked(); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h1, ex); + rs[1].assertNotInvoked(); + } + try { + assertEquals(null, h2.join()); + rs[2].assertInvoked(); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h2, ex); + rs[2].assertNotInvoked(); + } + try { + assertEquals(null, h3.join()); + rs[3].assertInvoked(); + } catch (CompletionException ok) { + checkCompletedWithWrappedException(h3, ex); + rs[3].assertNotInvoked(); + } + + checkCompletedNormally(f, v1); + checkCompletedExceptionally(g, ex); + }} + + /** + * runAfterEither result completes exceptionally if either source cancelled + */ + public void testRunAfterEither_sourceCancelled() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final Noop[] rs = new Noop[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new Noop(m); + + final CompletableFuture h0 = m.runAfterEither(f, g, rs[0]); + final CompletableFuture h1 = m.runAfterEither(g, f, rs[1]); + checkIncomplete(h0); + checkIncomplete(h1); + rs[0].assertNotInvoked(); + rs[1].assertNotInvoked(); + f.cancel(mayInterruptIfRunning); + checkCompletedWithWrappedCancellationException(h0); + checkCompletedWithWrappedCancellationException(h1); + final CompletableFuture h2 = m.runAfterEither(f, g, rs[2]); + final CompletableFuture h3 = m.runAfterEither(g, f, rs[3]); + checkCompletedWithWrappedCancellationException(h2); + checkCompletedWithWrappedCancellationException(h3); + + assertTrue(g.complete(v1)); + + // unspecified behavior - both source completions available + final CompletableFuture h4 = m.runAfterEither(f, g, rs[4]); + final CompletableFuture h5 = m.runAfterEither(g, f, rs[5]); + try { + assertNull(h4.join()); + rs[4].assertInvoked(); + } catch (CompletionException ok) { + checkCompletedWithWrappedCancellationException(h4); + rs[4].assertNotInvoked(); + } + try { + assertNull(h5.join()); + rs[5].assertInvoked(); + } catch (CompletionException ok) { + checkCompletedWithWrappedCancellationException(h5); + rs[5].assertNotInvoked(); + } + + checkCancelled(f); + checkCompletedNormally(g, v1); + checkCompletedWithWrappedCancellationException(h0); + checkCompletedWithWrappedCancellationException(h1); + checkCompletedWithWrappedCancellationException(h2); + checkCompletedWithWrappedCancellationException(h3); + for (int i = 0; i < 4; i++) rs[i].assertNotInvoked(); + }} + + /** + * runAfterEither result completes exceptionally if action does + */ + public void testRunAfterEither_actionFailed() { + for (ExecutionMode m : ExecutionMode.values()) + for (Integer v1 : new Integer[] { 1, null }) + for (Integer v2 : new Integer[] { 2, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final FailingRunnable[] rs = new FailingRunnable[6]; + for (int i = 0; i < rs.length; i++) rs[i] = new FailingRunnable(m); + + final CompletableFuture h0 = m.runAfterEither(f, g, rs[0]); + final CompletableFuture h1 = m.runAfterEither(g, f, rs[1]); + assertTrue(f.complete(v1)); + final CompletableFuture h2 = m.runAfterEither(f, g, rs[2]); + final CompletableFuture h3 = m.runAfterEither(g, f, rs[3]); + checkCompletedWithWrappedCFException(h0); + checkCompletedWithWrappedCFException(h1); + checkCompletedWithWrappedCFException(h2); + checkCompletedWithWrappedCFException(h3); + for (int i = 0; i < 4; i++) rs[i].assertInvoked(); + assertTrue(g.complete(v2)); + final CompletableFuture h4 = m.runAfterEither(f, g, rs[4]); + final CompletableFuture h5 = m.runAfterEither(g, f, rs[5]); + checkCompletedWithWrappedCFException(h4); + checkCompletedWithWrappedCFException(h5); + + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v2); + for (int i = 0; i < 6; i++) rs[i].assertInvoked(); + }} + + /** + * thenCompose result completes normally after normal completion of source + */ + public void testThenCompose_normalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean createIncomplete : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFutureInc r = new CompletableFutureInc(m); + if (!createIncomplete) assertTrue(f.complete(v1)); + final CompletableFuture g = m.thenCompose(f, r); + if (createIncomplete) assertTrue(f.complete(v1)); + + checkCompletedNormally(g, inc(v1)); + checkCompletedNormally(f, v1); + r.assertValue(v1); + }} + + /** + * thenCompose result completes exceptionally after exceptional + * completion of source + */ + public void testThenCompose_exceptionalCompletion() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean createIncomplete : new boolean[] { true, false }) + { + final CFException ex = new CFException(); + final CompletableFutureInc r = new CompletableFutureInc(m); + final CompletableFuture f = new CompletableFuture<>(); + if (!createIncomplete) f.completeExceptionally(ex); + final CompletableFuture g = m.thenCompose(f, r); + if (createIncomplete) f.completeExceptionally(ex); + + checkCompletedWithWrappedException(g, ex); + checkCompletedExceptionally(f, ex); + r.assertNotInvoked(); + }} + + /** + * thenCompose result completes exceptionally if action does + */ + public void testThenCompose_actionFailed() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean createIncomplete : new boolean[] { true, false }) + for (Integer v1 : new Integer[] { 1, null }) + { + final CompletableFuture f = new CompletableFuture<>(); + final FailingCompletableFutureFunction r + = new FailingCompletableFutureFunction(m); + if (!createIncomplete) assertTrue(f.complete(v1)); + final CompletableFuture g = m.thenCompose(f, r); + if (createIncomplete) assertTrue(f.complete(v1)); + + checkCompletedWithWrappedCFException(g); + checkCompletedNormally(f, v1); + }} + + /** + * thenCompose result completes exceptionally if source cancelled + */ + public void testThenCompose_sourceCancelled() { + for (ExecutionMode m : ExecutionMode.values()) + for (boolean createIncomplete : new boolean[] { true, false }) + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + { + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFutureInc r = new CompletableFutureInc(m); + if (!createIncomplete) assertTrue(f.cancel(mayInterruptIfRunning)); + final CompletableFuture g = m.thenCompose(f, r); + if (createIncomplete) { + checkIncomplete(g); + assertTrue(f.cancel(mayInterruptIfRunning)); + } + + checkCompletedWithWrappedCancellationException(g); + checkCancelled(f); + }} + + /** + * thenCompose result completes exceptionally if the result of the action does + */ + public void testThenCompose_actionReturnsFailingFuture() { + for (ExecutionMode m : ExecutionMode.values()) + for (int order = 0; order < 6; order++) + for (Integer v1 : new Integer[] { 1, null }) + { + final CFException ex = new CFException(); + final CompletableFuture f = new CompletableFuture<>(); + final CompletableFuture g = new CompletableFuture<>(); + final CompletableFuture h; + // Test all permutations of orders + switch (order) { + case 0: + assertTrue(f.complete(v1)); + assertTrue(g.completeExceptionally(ex)); + h = m.thenCompose(f, (x -> g)); + break; + case 1: + assertTrue(f.complete(v1)); + h = m.thenCompose(f, (x -> g)); + assertTrue(g.completeExceptionally(ex)); + break; + case 2: + assertTrue(g.completeExceptionally(ex)); + assertTrue(f.complete(v1)); + h = m.thenCompose(f, (x -> g)); + break; + case 3: + assertTrue(g.completeExceptionally(ex)); + h = m.thenCompose(f, (x -> g)); + assertTrue(f.complete(v1)); + break; + case 4: + h = m.thenCompose(f, (x -> g)); + assertTrue(f.complete(v1)); + assertTrue(g.completeExceptionally(ex)); + break; + case 5: + h = m.thenCompose(f, (x -> g)); + assertTrue(f.complete(v1)); + assertTrue(g.completeExceptionally(ex)); + break; + default: throw new AssertionError(); + } + + checkCompletedExceptionally(g, ex); + checkCompletedWithWrappedException(h, ex); + checkCompletedNormally(f, v1); + }} + + // other static methods + + /** + * allOf(no component futures) returns a future completed normally + * with the value null + */ + public void testAllOf_empty() throws Exception { + CompletableFuture f = CompletableFuture.allOf(); + checkCompletedNormally(f, null); + } + + /** + * allOf returns a future completed normally with the value null + * when all components complete normally + */ + public void testAllOf_normal() throws Exception { + for (int k = 1; k < 10; k++) { + CompletableFuture[] fs + = (CompletableFuture[]) new CompletableFuture[k]; + for (int i = 0; i < k; i++) + fs[i] = new CompletableFuture<>(); + CompletableFuture f = CompletableFuture.allOf(fs); + for (int i = 0; i < k; i++) { + checkIncomplete(f); + checkIncomplete(CompletableFuture.allOf(fs)); + fs[i].complete(one); + } + checkCompletedNormally(f, null); + checkCompletedNormally(CompletableFuture.allOf(fs), null); + } + } + + public void testAllOf_backwards() throws Exception { + for (int k = 1; k < 10; k++) { + CompletableFuture[] fs + = (CompletableFuture[]) new CompletableFuture[k]; + for (int i = 0; i < k; i++) + fs[i] = new CompletableFuture<>(); + CompletableFuture f = CompletableFuture.allOf(fs); + for (int i = k - 1; i >= 0; i--) { + checkIncomplete(f); + checkIncomplete(CompletableFuture.allOf(fs)); + fs[i].complete(one); + } + checkCompletedNormally(f, null); + checkCompletedNormally(CompletableFuture.allOf(fs), null); + } + } + + public void testAllOf_exceptional() throws Exception { + for (int k = 1; k < 10; k++) { + CompletableFuture[] fs + = (CompletableFuture[]) new CompletableFuture[k]; + CFException ex = new CFException(); + for (int i = 0; i < k; i++) + fs[i] = new CompletableFuture<>(); + CompletableFuture f = CompletableFuture.allOf(fs); + for (int i = 0; i < k; i++) { + checkIncomplete(f); + checkIncomplete(CompletableFuture.allOf(fs)); + if (i != k / 2) { + fs[i].complete(i); + checkCompletedNormally(fs[i], i); + } else { + fs[i].completeExceptionally(ex); + checkCompletedExceptionally(fs[i], ex); + } + } + checkCompletedWithWrappedException(f, ex); + checkCompletedWithWrappedException(CompletableFuture.allOf(fs), ex); + } + } + + /** + * anyOf(no component futures) returns an incomplete future + */ + public void testAnyOf_empty() throws Exception { + for (Integer v1 : new Integer[] { 1, null }) + { + CompletableFuture f = CompletableFuture.anyOf(); + checkIncomplete(f); + + f.complete(v1); + checkCompletedNormally(f, v1); + }} + + /** + * anyOf returns a future completed normally with a value when + * a component future does + */ + public void testAnyOf_normal() throws Exception { + for (int k = 0; k < 10; k++) { + CompletableFuture[] fs = new CompletableFuture[k]; + for (int i = 0; i < k; i++) + fs[i] = new CompletableFuture<>(); + CompletableFuture f = CompletableFuture.anyOf(fs); + checkIncomplete(f); + for (int i = 0; i < k; i++) { + fs[i].complete(i); + checkCompletedNormally(f, 0); + int x = (int) CompletableFuture.anyOf(fs).join(); + assertTrue(0 <= x && x <= i); + } + } + } + public void testAnyOf_normal_backwards() throws Exception { + for (int k = 0; k < 10; k++) { + CompletableFuture[] fs = new CompletableFuture[k]; + for (int i = 0; i < k; i++) + fs[i] = new CompletableFuture<>(); + CompletableFuture f = CompletableFuture.anyOf(fs); + checkIncomplete(f); + for (int i = k - 1; i >= 0; i--) { + fs[i].complete(i); + checkCompletedNormally(f, k - 1); + int x = (int) CompletableFuture.anyOf(fs).join(); + assertTrue(i <= x && x <= k - 1); + } + } + } + + /** + * anyOf result completes exceptionally when any component does. + */ + public void testAnyOf_exceptional() throws Exception { + for (int k = 0; k < 10; k++) { + CompletableFuture[] fs = new CompletableFuture[k]; + CFException[] exs = new CFException[k]; + for (int i = 0; i < k; i++) { + fs[i] = new CompletableFuture<>(); + exs[i] = new CFException(); + } + CompletableFuture f = CompletableFuture.anyOf(fs); + checkIncomplete(f); + for (int i = 0; i < k; i++) { + fs[i].completeExceptionally(exs[i]); + checkCompletedWithWrappedException(f, exs[0]); + checkCompletedWithWrappedCFException(CompletableFuture.anyOf(fs)); + } + } + } + + public void testAnyOf_exceptional_backwards() throws Exception { + for (int k = 0; k < 10; k++) { + CompletableFuture[] fs = new CompletableFuture[k]; + CFException[] exs = new CFException[k]; + for (int i = 0; i < k; i++) { + fs[i] = new CompletableFuture<>(); + exs[i] = new CFException(); + } + CompletableFuture f = CompletableFuture.anyOf(fs); + checkIncomplete(f); + for (int i = k - 1; i >= 0; i--) { + fs[i].completeExceptionally(exs[i]); + checkCompletedWithWrappedException(f, exs[k - 1]); + checkCompletedWithWrappedCFException(CompletableFuture.anyOf(fs)); + } + } + } + + /** + * Completion methods throw NullPointerException with null arguments + */ + public void testNPE() { + CompletableFuture f = new CompletableFuture<>(); + CompletableFuture g = new CompletableFuture<>(); + CompletableFuture nullFuture = (CompletableFuture)null; + ThreadExecutor exec = new ThreadExecutor(); + + Runnable[] throwingActions = { + () -> CompletableFuture.supplyAsync(null), + () -> CompletableFuture.supplyAsync(null, exec), + () -> CompletableFuture.supplyAsync(new IntegerSupplier(ExecutionMode.SYNC, 42), null), + + () -> CompletableFuture.runAsync(null), + () -> CompletableFuture.runAsync(null, exec), + () -> CompletableFuture.runAsync(() -> {}, null), + + () -> f.completeExceptionally(null), + + () -> f.thenApply(null), + () -> f.thenApplyAsync(null), + () -> f.thenApplyAsync((x) -> x, null), + () -> f.thenApplyAsync(null, exec), + + () -> f.thenAccept(null), + () -> f.thenAcceptAsync(null), + () -> f.thenAcceptAsync((x) -> {} , null), + () -> f.thenAcceptAsync(null, exec), + + () -> f.thenRun(null), + () -> f.thenRunAsync(null), + () -> f.thenRunAsync(() -> {} , null), + () -> f.thenRunAsync(null, exec), + + () -> f.thenCombine(g, null), + () -> f.thenCombineAsync(g, null), + () -> f.thenCombineAsync(g, null, exec), + () -> f.thenCombine(nullFuture, (x, y) -> x), + () -> f.thenCombineAsync(nullFuture, (x, y) -> x), + () -> f.thenCombineAsync(nullFuture, (x, y) -> x, exec), + () -> f.thenCombineAsync(g, (x, y) -> x, null), + + () -> f.thenAcceptBoth(g, null), + () -> f.thenAcceptBothAsync(g, null), + () -> f.thenAcceptBothAsync(g, null, exec), + () -> f.thenAcceptBoth(nullFuture, (x, y) -> {}), + () -> f.thenAcceptBothAsync(nullFuture, (x, y) -> {}), + () -> f.thenAcceptBothAsync(nullFuture, (x, y) -> {}, exec), + () -> f.thenAcceptBothAsync(g, (x, y) -> {}, null), + + () -> f.runAfterBoth(g, null), + () -> f.runAfterBothAsync(g, null), + () -> f.runAfterBothAsync(g, null, exec), + () -> f.runAfterBoth(nullFuture, () -> {}), + () -> f.runAfterBothAsync(nullFuture, () -> {}), + () -> f.runAfterBothAsync(nullFuture, () -> {}, exec), + () -> f.runAfterBothAsync(g, () -> {}, null), + + () -> f.applyToEither(g, null), + () -> f.applyToEitherAsync(g, null), + () -> f.applyToEitherAsync(g, null, exec), + () -> f.applyToEither(nullFuture, (x) -> x), + () -> f.applyToEitherAsync(nullFuture, (x) -> x), + () -> f.applyToEitherAsync(nullFuture, (x) -> x, exec), + () -> f.applyToEitherAsync(g, (x) -> x, null), + + () -> f.acceptEither(g, null), + () -> f.acceptEitherAsync(g, null), + () -> f.acceptEitherAsync(g, null, exec), + () -> f.acceptEither(nullFuture, (x) -> {}), + () -> f.acceptEitherAsync(nullFuture, (x) -> {}), + () -> f.acceptEitherAsync(nullFuture, (x) -> {}, exec), + () -> f.acceptEitherAsync(g, (x) -> {}, null), + + () -> f.runAfterEither(g, null), + () -> f.runAfterEitherAsync(g, null), + () -> f.runAfterEitherAsync(g, null, exec), + () -> f.runAfterEither(nullFuture, () -> {}), + () -> f.runAfterEitherAsync(nullFuture, () -> {}), + () -> f.runAfterEitherAsync(nullFuture, () -> {}, exec), + () -> f.runAfterEitherAsync(g, () -> {}, null), + + () -> f.thenCompose(null), + () -> f.thenComposeAsync(null), + () -> f.thenComposeAsync(new CompletableFutureInc(ExecutionMode.EXECUTOR), null), + () -> f.thenComposeAsync(null, exec), + + () -> f.exceptionally(null), + + () -> f.handle(null), + + () -> CompletableFuture.allOf((CompletableFuture)null), + () -> CompletableFuture.allOf((CompletableFuture[])null), + () -> CompletableFuture.allOf(f, null), + () -> CompletableFuture.allOf(null, f), + + () -> CompletableFuture.anyOf((CompletableFuture)null), + () -> CompletableFuture.anyOf((CompletableFuture[])null), + () -> CompletableFuture.anyOf(f, null), + () -> CompletableFuture.anyOf(null, f), + + () -> f.obtrudeException(null), + + () -> CompletableFuture.delayedExecutor(1L, SECONDS, null), + () -> CompletableFuture.delayedExecutor(1L, null, new ThreadExecutor()), + () -> CompletableFuture.delayedExecutor(1L, null), + + () -> f.orTimeout(1L, null), + () -> f.completeOnTimeout(42, 1L, null), + + () -> CompletableFuture.failedFuture(null), + () -> CompletableFuture.failedStage(null), + }; + + assertThrows(NullPointerException.class, throwingActions); + assertEquals(0, exec.count.get()); + } + + /** + * toCompletableFuture returns this CompletableFuture. + */ + public void testToCompletableFuture() { + CompletableFuture f = new CompletableFuture<>(); + assertSame(f, f.toCompletableFuture()); + } + + // jdk9 + + /** + * newIncompleteFuture returns an incomplete CompletableFuture + */ + public void testNewIncompleteFuture() { + for (Integer v1 : new Integer[] { 1, null }) + { + CompletableFuture f = new CompletableFuture<>(); + CompletableFuture g = f.newIncompleteFuture(); + checkIncomplete(f); + checkIncomplete(g); + f.complete(v1); + checkCompletedNormally(f, v1); + checkIncomplete(g); + g.complete(v1); + checkCompletedNormally(g, v1); + assertSame(g.getClass(), CompletableFuture.class); + }} + + /** + * completedStage returns a completed CompletionStage + */ + public void testCompletedStage() { + AtomicInteger x = new AtomicInteger(0); + AtomicReference r = new AtomicReference(); + CompletionStage f = CompletableFuture.completedStage(1); + f.whenComplete((v, e) -> {if (e != null) r.set(e); else x.set(v);}); + assertEquals(x.get(), 1); + assertNull(r.get()); + } + + /** + * defaultExecutor by default returns the commonPool if + * it supports more than one thread. + */ + public void testDefaultExecutor() { + CompletableFuture f = new CompletableFuture<>(); + Executor e = f.defaultExecutor(); + Executor c = ForkJoinPool.commonPool(); + if (ForkJoinPool.getCommonPoolParallelism() > 1) + assertSame(e, c); + else + assertNotSame(e, c); + } + + /** + * failedFuture returns a CompletableFuture completed + * exceptionally with the given Exception + */ + public void testFailedFuture() { + CFException ex = new CFException(); + CompletableFuture f = CompletableFuture.failedFuture(ex); + checkCompletedExceptionally(f, ex); + } + + /** + * failedFuture(null) throws NPE + */ + public void testFailedFuture_null() { + try { + CompletableFuture f = CompletableFuture.failedFuture(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * copy returns a CompletableFuture that is completed normally, + * with the same value, when source is. + */ + public void testCopy() { + CompletableFuture f = new CompletableFuture<>(); + CompletableFuture g = f.copy(); + checkIncomplete(f); + checkIncomplete(g); + f.complete(1); + checkCompletedNormally(f, 1); + checkCompletedNormally(g, 1); + } + + /** + * copy returns a CompletableFuture that is completed exceptionally + * when source is. + */ + public void testCopy2() { + CompletableFuture f = new CompletableFuture<>(); + CompletableFuture g = f.copy(); + checkIncomplete(f); + checkIncomplete(g); + CFException ex = new CFException(); + f.completeExceptionally(ex); + checkCompletedExceptionally(f, ex); + checkCompletedWithWrappedException(g, ex); + } + + /** + * minimalCompletionStage returns a CompletableFuture that is + * completed normally, with the same value, when source is. + */ + public void testMinimalCompletionStage() { + CompletableFuture f = new CompletableFuture<>(); + CompletionStage g = f.minimalCompletionStage(); + AtomicInteger x = new AtomicInteger(0); + AtomicReference r = new AtomicReference(); + checkIncomplete(f); + g.whenComplete((v, e) -> {if (e != null) r.set(e); else x.set(v);}); + f.complete(1); + checkCompletedNormally(f, 1); + assertEquals(x.get(), 1); + assertNull(r.get()); + } + + /** + * minimalCompletionStage returns a CompletableFuture that is + * completed exceptionally when source is. + */ + public void testMinimalCompletionStage2() { + CompletableFuture f = new CompletableFuture<>(); + CompletionStage g = f.minimalCompletionStage(); + AtomicInteger x = new AtomicInteger(0); + AtomicReference r = new AtomicReference(); + g.whenComplete((v, e) -> {if (e != null) r.set(e); else x.set(v);}); + checkIncomplete(f); + CFException ex = new CFException(); + f.completeExceptionally(ex); + checkCompletedExceptionally(f, ex); + assertEquals(x.get(), 0); + assertEquals(r.get().getCause(), ex); + } + + /** + * failedStage returns a CompletionStage completed + * exceptionally with the given Exception + */ + public void testFailedStage() { + CFException ex = new CFException(); + CompletionStage f = CompletableFuture.failedStage(ex); + AtomicInteger x = new AtomicInteger(0); + AtomicReference r = new AtomicReference(); + f.whenComplete((v, e) -> {if (e != null) r.set(e); else x.set(v);}); + assertEquals(x.get(), 0); + assertEquals(r.get(), ex); + } + + /** + * completeAsync completes with value of given supplier + */ + public void testCompleteAsync() { + for (Integer v1 : new Integer[] { 1, null }) + { + CompletableFuture f = new CompletableFuture<>(); + f.completeAsync(() -> v1); + f.join(); + checkCompletedNormally(f, v1); + }} + + /** + * completeAsync completes exceptionally if given supplier throws + */ + public void testCompleteAsync2() { + CompletableFuture f = new CompletableFuture<>(); + CFException ex = new CFException(); + f.completeAsync(() -> {if (true) throw ex; return 1;}); + try { + f.join(); + shouldThrow(); + } catch (CompletionException success) {} + checkCompletedWithWrappedException(f, ex); + } + + /** + * completeAsync with given executor completes with value of given supplier + */ + public void testCompleteAsync3() { + for (Integer v1 : new Integer[] { 1, null }) + { + CompletableFuture f = new CompletableFuture<>(); + ThreadExecutor executor = new ThreadExecutor(); + f.completeAsync(() -> v1, executor); + assertSame(v1, f.join()); + checkCompletedNormally(f, v1); + assertEquals(1, executor.count.get()); + }} + + /** + * completeAsync with given executor completes exceptionally if + * given supplier throws + */ + public void testCompleteAsync4() { + CompletableFuture f = new CompletableFuture<>(); + CFException ex = new CFException(); + ThreadExecutor executor = new ThreadExecutor(); + f.completeAsync(() -> {if (true) throw ex; return 1;}, executor); + try { + f.join(); + shouldThrow(); + } catch (CompletionException success) {} + checkCompletedWithWrappedException(f, ex); + assertEquals(1, executor.count.get()); + } + + /** + * orTimeout completes with TimeoutException if not complete + */ + public void testOrTimeout_timesOut() { + long timeoutMillis = timeoutMillis(); + CompletableFuture f = new CompletableFuture<>(); + long startTime = System.nanoTime(); + f.orTimeout(timeoutMillis, MILLISECONDS); + checkCompletedWithTimeoutException(f); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + } + + /** + * orTimeout completes normally if completed before timeout + */ + public void testOrTimeout_completed() { + for (Integer v1 : new Integer[] { 1, null }) + { + CompletableFuture f = new CompletableFuture<>(); + CompletableFuture g = new CompletableFuture<>(); + long startTime = System.nanoTime(); + f.complete(v1); + f.orTimeout(LONG_DELAY_MS, MILLISECONDS); + g.orTimeout(LONG_DELAY_MS, MILLISECONDS); + g.complete(v1); + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v1); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS / 2); + }} + + /** + * completeOnTimeout completes with given value if not complete + */ + public void testCompleteOnTimeout_timesOut() { + testInParallel(() -> testCompleteOnTimeout_timesOut(42), + () -> testCompleteOnTimeout_timesOut(null)); + } + + public void testCompleteOnTimeout_timesOut(Integer v) { + long timeoutMillis = timeoutMillis(); + CompletableFuture f = new CompletableFuture<>(); + long startTime = System.nanoTime(); + f.completeOnTimeout(v, timeoutMillis, MILLISECONDS); + assertSame(v, f.join()); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + f.complete(99); // should have no effect + checkCompletedNormally(f, v); + } + + /** + * completeOnTimeout has no effect if completed within timeout + */ + public void testCompleteOnTimeout_completed() { + for (Integer v1 : new Integer[] { 1, null }) + { + CompletableFuture f = new CompletableFuture<>(); + CompletableFuture g = new CompletableFuture<>(); + long startTime = System.nanoTime(); + f.complete(v1); + f.completeOnTimeout(-1, LONG_DELAY_MS, MILLISECONDS); + g.completeOnTimeout(-1, LONG_DELAY_MS, MILLISECONDS); + g.complete(v1); + checkCompletedNormally(f, v1); + checkCompletedNormally(g, v1); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS / 2); + }} + + /** + * delayedExecutor returns an executor that delays submission + */ + public void testDelayedExecutor() { + testInParallel(() -> testDelayedExecutor(null, null), + () -> testDelayedExecutor(null, 1), + () -> testDelayedExecutor(new ThreadExecutor(), 1), + () -> testDelayedExecutor(new ThreadExecutor(), 1)); + } + + public void testDelayedExecutor(Executor executor, Integer v) throws Exception { + long timeoutMillis = timeoutMillis(); + // Use an "unreasonably long" long timeout to catch lingering threads + long longTimeoutMillis = 1000 * 60 * 60 * 24; + final Executor delayer, longDelayer; + if (executor == null) { + delayer = CompletableFuture.delayedExecutor(timeoutMillis, MILLISECONDS); + longDelayer = CompletableFuture.delayedExecutor(longTimeoutMillis, MILLISECONDS); + } else { + delayer = CompletableFuture.delayedExecutor(timeoutMillis, MILLISECONDS, executor); + longDelayer = CompletableFuture.delayedExecutor(longTimeoutMillis, MILLISECONDS, executor); + } + long startTime = System.nanoTime(); + CompletableFuture f = + CompletableFuture.supplyAsync(() -> v, delayer); + CompletableFuture g = + CompletableFuture.supplyAsync(() -> v, longDelayer); + + assertNull(g.getNow(null)); + + assertSame(v, f.get(LONG_DELAY_MS, MILLISECONDS)); + long millisElapsed = millisElapsedSince(startTime); + assertTrue(millisElapsed >= timeoutMillis); + assertTrue(millisElapsed < LONG_DELAY_MS / 2); + + checkCompletedNormally(f, v); + + checkIncomplete(g); + assertTrue(g.cancel(true)); + } + + //--- tests of implementation details; not part of official tck --- + + Object resultOf(CompletableFuture f) { + try { + java.lang.reflect.Field resultField + = CompletableFuture.class.getDeclaredField("result"); + resultField.setAccessible(true); + return resultField.get(f); + } catch (Throwable t) { throw new AssertionError(t); } + } + + public void testExceptionPropagationReusesResultObject() { + if (!testImplementationDetails) return; + for (ExecutionMode m : ExecutionMode.values()) + { + final CFException ex = new CFException(); + final CompletableFuture v42 = CompletableFuture.completedFuture(42); + final CompletableFuture incomplete = new CompletableFuture<>(); + + List, CompletableFuture>> funs + = new ArrayList<>(); + + funs.add((y) -> m.thenRun(y, new Noop(m))); + funs.add((y) -> m.thenAccept(y, new NoopConsumer(m))); + funs.add((y) -> m.thenApply(y, new IncFunction(m))); + + funs.add((y) -> m.runAfterEither(y, incomplete, new Noop(m))); + funs.add((y) -> m.acceptEither(y, incomplete, new NoopConsumer(m))); + funs.add((y) -> m.applyToEither(y, incomplete, new IncFunction(m))); + + funs.add((y) -> m.runAfterBoth(y, v42, new Noop(m))); + funs.add((y) -> m.thenAcceptBoth(y, v42, new SubtractAction(m))); + funs.add((y) -> m.thenCombine(y, v42, new SubtractFunction(m))); + + funs.add((y) -> m.whenComplete(y, (Integer r, Throwable t) -> {})); + + funs.add((y) -> m.thenCompose(y, new CompletableFutureInc(m))); + + funs.add((y) -> CompletableFuture.allOf(new CompletableFuture[] {y, v42})); + funs.add((y) -> CompletableFuture.anyOf(new CompletableFuture[] {y, incomplete})); + + for (Function, CompletableFuture> + fun : funs) { + CompletableFuture f = new CompletableFuture<>(); + f.completeExceptionally(ex); + CompletableFuture src = m.thenApply(f, new IncFunction(m)); + checkCompletedWithWrappedException(src, ex); + CompletableFuture dep = fun.apply(src); + checkCompletedWithWrappedException(dep, ex); + assertSame(resultOf(src), resultOf(dep)); + } + + for (Function, CompletableFuture> + fun : funs) { + CompletableFuture f = new CompletableFuture<>(); + CompletableFuture src = m.thenApply(f, new IncFunction(m)); + CompletableFuture dep = fun.apply(src); + f.completeExceptionally(ex); + checkCompletedWithWrappedException(src, ex); + checkCompletedWithWrappedException(dep, ex); + assertSame(resultOf(src), resultOf(dep)); + } + + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + for (Function, CompletableFuture> + fun : funs) { + CompletableFuture f = new CompletableFuture<>(); + f.cancel(mayInterruptIfRunning); + checkCancelled(f); + CompletableFuture src = m.thenApply(f, new IncFunction(m)); + checkCompletedWithWrappedCancellationException(src); + CompletableFuture dep = fun.apply(src); + checkCompletedWithWrappedCancellationException(dep); + assertSame(resultOf(src), resultOf(dep)); + } + + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) + for (Function, CompletableFuture> + fun : funs) { + CompletableFuture f = new CompletableFuture<>(); + CompletableFuture src = m.thenApply(f, new IncFunction(m)); + CompletableFuture dep = fun.apply(src); + f.cancel(mayInterruptIfRunning); + checkCancelled(f); + checkCompletedWithWrappedCancellationException(src); + checkCompletedWithWrappedCancellationException(dep); + assertSame(resultOf(src), resultOf(dep)); + } + }} + + /** + * Minimal completion stages throw UOE for all non-CompletionStage methods + */ + public void testMinimalCompletionStage_minimality() { + if (!testImplementationDetails) return; + Function toSignature = + (method) -> method.getName() + Arrays.toString(method.getParameterTypes()); + Predicate isNotStatic = + (method) -> (method.getModifiers() & Modifier.STATIC) == 0; + List minimalMethods = + Stream.of(Object.class, CompletionStage.class) + .flatMap((klazz) -> Stream.of(klazz.getMethods())) + .filter(isNotStatic) + .collect(Collectors.toList()); + // Methods from CompletableFuture permitted NOT to throw UOE + String[] signatureWhitelist = { + "newIncompleteFuture[]", + "defaultExecutor[]", + "minimalCompletionStage[]", + "copy[]", + }; + Set permittedMethodSignatures = + Stream.concat(minimalMethods.stream().map(toSignature), + Stream.of(signatureWhitelist)) + .collect(Collectors.toSet()); + List allMethods = Stream.of(CompletableFuture.class.getMethods()) + .filter(isNotStatic) + .filter((method) -> !permittedMethodSignatures.contains(toSignature.apply(method))) + .collect(Collectors.toList()); + + CompletionStage minimalStage = + new CompletableFuture().minimalCompletionStage(); + + List bugs = new ArrayList<>(); + for (Method method : allMethods) { + Class[] parameterTypes = method.getParameterTypes(); + Object[] args = new Object[parameterTypes.length]; + // Manufacture boxed primitives for primitive params + for (int i = 0; i < args.length; i++) { + Class type = parameterTypes[i]; + if (parameterTypes[i] == boolean.class) + args[i] = false; + else if (parameterTypes[i] == int.class) + args[i] = 0; + else if (parameterTypes[i] == long.class) + args[i] = 0L; + } + try { + method.invoke(minimalStage, args); + bugs.add(method); + } + catch (java.lang.reflect.InvocationTargetException expected) { + if (! (expected.getCause() instanceof UnsupportedOperationException)) { + bugs.add(method); + // expected.getCause().printStackTrace(); + } + } + catch (ReflectiveOperationException bad) { throw new Error(bad); } + } + if (!bugs.isEmpty()) + throw new Error("Methods did not throw UOE: " + bugs.toString()); + } + + static class Monad { + static class ZeroException extends RuntimeException { + public ZeroException() { super("monadic zero"); } + } + // "return", "unit" + static CompletableFuture unit(T value) { + return completedFuture(value); + } + // monadic zero ? + static CompletableFuture zero() { + return failedFuture(new ZeroException()); + } + // >=> + static Function> compose + (Function> f, + Function> g) { + return (x) -> f.apply(x).thenCompose(g); + } + + static void assertZero(CompletableFuture f) { + try { + f.getNow(null); + throw new AssertionFailedError("should throw"); + } catch (CompletionException success) { + assertTrue(success.getCause() instanceof ZeroException); + } + } + + static void assertFutureEquals(CompletableFuture f, + CompletableFuture g) { + T fval = null, gval = null; + Throwable fex = null, gex = null; + + try { fval = f.get(); } + catch (ExecutionException ex) { fex = ex.getCause(); } + catch (Throwable ex) { fex = ex; } + + try { gval = g.get(); } + catch (ExecutionException ex) { gex = ex.getCause(); } + catch (Throwable ex) { gex = ex; } + + if (fex != null || gex != null) + assertSame(fex.getClass(), gex.getClass()); + else + assertEquals(fval, gval); + } + + static class PlusFuture extends CompletableFuture { + AtomicReference firstFailure = new AtomicReference<>(null); + } + + /** Implements "monadic plus". */ + static CompletableFuture plus(CompletableFuture f, + CompletableFuture g) { + PlusFuture plus = new PlusFuture(); + BiConsumer action = (T result, Throwable ex) -> { + try { + if (ex == null) { + if (plus.complete(result)) + if (plus.firstFailure.get() != null) + plus.firstFailure.set(null); + } + else if (plus.firstFailure.compareAndSet(null, ex)) { + if (plus.isDone()) + plus.firstFailure.set(null); + } + else { + // first failure has precedence + Throwable first = plus.firstFailure.getAndSet(null); + + // may fail with "Self-suppression not permitted" + try { first.addSuppressed(ex); } + catch (Exception ignored) {} + + plus.completeExceptionally(first); + } + } catch (Throwable unexpected) { + plus.completeExceptionally(unexpected); + } + }; + f.whenComplete(action); + g.whenComplete(action); + return plus; + } + } + + /** + * CompletableFuture is an additive monad - sort of. + * https://en.wikipedia.org/wiki/Monad_(functional_programming)#Additive_monads + */ + public void testAdditiveMonad() throws Throwable { + Function> unit = Monad::unit; + CompletableFuture zero = Monad.zero(); + + // Some mutually non-commutative functions + Function> triple + = (x) -> Monad.unit(3 * x); + Function> inc + = (x) -> Monad.unit(x + 1); + + // unit is a right identity: m >>= unit === m + Monad.assertFutureEquals(inc.apply(5L).thenCompose(unit), + inc.apply(5L)); + // unit is a left identity: (unit x) >>= f === f x + Monad.assertFutureEquals(unit.apply(5L).thenCompose(inc), + inc.apply(5L)); + + // associativity: (m >>= f) >>= g === m >>= ( \x -> (f x >>= g) ) + Monad.assertFutureEquals( + unit.apply(5L).thenCompose(inc).thenCompose(triple), + unit.apply(5L).thenCompose((x) -> inc.apply(x).thenCompose(triple))); + + // The case for CompletableFuture as an additive monad is weaker... + + // zero is a monadic zero + Monad.assertZero(zero); + + // left zero: zero >>= f === zero + Monad.assertZero(zero.thenCompose(inc)); + // right zero: f >>= (\x -> zero) === zero + Monad.assertZero(inc.apply(5L).thenCompose((x) -> zero)); + + // f plus zero === f + Monad.assertFutureEquals(Monad.unit(5L), + Monad.plus(Monad.unit(5L), zero)); + // zero plus f === f + Monad.assertFutureEquals(Monad.unit(5L), + Monad.plus(zero, Monad.unit(5L))); + // zero plus zero === zero + Monad.assertZero(Monad.plus(zero, zero)); + { + CompletableFuture f = Monad.plus(Monad.unit(5L), + Monad.unit(8L)); + // non-determinism + assertTrue(f.get() == 5L || f.get() == 8L); + } + + CompletableFuture godot = new CompletableFuture<>(); + // f plus godot === f (doesn't wait for godot) + Monad.assertFutureEquals(Monad.unit(5L), + Monad.plus(Monad.unit(5L), godot)); + // godot plus f === f (doesn't wait for godot) + Monad.assertFutureEquals(Monad.unit(5L), + Monad.plus(godot, Monad.unit(5L))); + } + +// static U join(CompletionStage stage) { +// CompletableFuture f = new CompletableFuture<>(); +// stage.whenComplete((v, ex) -> { +// if (ex != null) f.completeExceptionally(ex); else f.complete(v); +// }); +// return f.join(); +// } + +// static boolean isDone(CompletionStage stage) { +// CompletableFuture f = new CompletableFuture<>(); +// stage.whenComplete((v, ex) -> { +// if (ex != null) f.completeExceptionally(ex); else f.complete(v); +// }); +// return f.isDone(); +// } + +// static U join2(CompletionStage stage) { +// return stage.toCompletableFuture().copy().join(); +// } + +// static boolean isDone2(CompletionStage stage) { +// return stage.toCompletableFuture().copy().isDone(); +// } + +} diff --git a/jdk/test/java/util/concurrent/tck/ConcurrentHashMap8Test.java b/jdk/test/java/util/concurrent/tck/ConcurrentHashMap8Test.java new file mode 100644 index 00000000000..518fab33170 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ConcurrentHashMap8Test.java @@ -0,0 +1,1118 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import static java.util.Spliterator.CONCURRENT; +import static java.util.Spliterator.DISTINCT; +import static java.util.Spliterator.NONNULL; + +import java.util.AbstractMap; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.Spliterator; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.LongAdder; +import java.util.function.BiFunction; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ConcurrentHashMap8Test extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(ConcurrentHashMap8Test.class); + } + + /** + * Returns a new map from Integers 1-5 to Strings "A"-"E". + */ + private static ConcurrentHashMap map5() { + ConcurrentHashMap map = new ConcurrentHashMap(5); + assertTrue(map.isEmpty()); + map.put(one, "A"); + map.put(two, "B"); + map.put(three, "C"); + map.put(four, "D"); + map.put(five, "E"); + assertFalse(map.isEmpty()); + assertEquals(5, map.size()); + return map; + } + + /** + * getOrDefault returns value if present, else default + */ + public void testGetOrDefault() { + ConcurrentHashMap map = map5(); + assertEquals(map.getOrDefault(one, "Z"), "A"); + assertEquals(map.getOrDefault(six, "Z"), "Z"); + } + + /** + * computeIfAbsent adds when the given key is not present + */ + public void testComputeIfAbsent() { + ConcurrentHashMap map = map5(); + map.computeIfAbsent(six, (x) -> "Z"); + assertTrue(map.containsKey(six)); + } + + /** + * computeIfAbsent does not replace if the key is already present + */ + public void testComputeIfAbsent2() { + ConcurrentHashMap map = map5(); + assertEquals("A", map.computeIfAbsent(one, (x) -> "Z")); + } + + /** + * computeIfAbsent does not add if function returns null + */ + public void testComputeIfAbsent3() { + ConcurrentHashMap map = map5(); + map.computeIfAbsent(six, (x) -> null); + assertFalse(map.containsKey(six)); + } + + /** + * computeIfPresent does not replace if the key is already present + */ + public void testComputeIfPresent() { + ConcurrentHashMap map = map5(); + map.computeIfPresent(six, (x, y) -> "Z"); + assertFalse(map.containsKey(six)); + } + + /** + * computeIfPresent adds when the given key is not present + */ + public void testComputeIfPresent2() { + ConcurrentHashMap map = map5(); + assertEquals("Z", map.computeIfPresent(one, (x, y) -> "Z")); + } + + /** + * compute does not replace if the function returns null + */ + public void testCompute() { + ConcurrentHashMap map = map5(); + map.compute(six, (x, y) -> null); + assertFalse(map.containsKey(six)); + } + + /** + * compute adds when the given key is not present + */ + public void testCompute2() { + ConcurrentHashMap map = map5(); + assertEquals("Z", map.compute(six, (x, y) -> "Z")); + } + + /** + * compute replaces when the given key is present + */ + public void testCompute3() { + ConcurrentHashMap map = map5(); + assertEquals("Z", map.compute(one, (x, y) -> "Z")); + } + + /** + * compute removes when the given key is present and function returns null + */ + public void testCompute4() { + ConcurrentHashMap map = map5(); + map.compute(one, (x, y) -> null); + assertFalse(map.containsKey(one)); + } + + /** + * merge adds when the given key is not present + */ + public void testMerge1() { + ConcurrentHashMap map = map5(); + assertEquals("Y", map.merge(six, "Y", (x, y) -> "Z")); + } + + /** + * merge replaces when the given key is present + */ + public void testMerge2() { + ConcurrentHashMap map = map5(); + assertEquals("Z", map.merge(one, "Y", (x, y) -> "Z")); + } + + /** + * merge removes when the given key is present and function returns null + */ + public void testMerge3() { + ConcurrentHashMap map = map5(); + map.merge(one, "Y", (x, y) -> null); + assertFalse(map.containsKey(one)); + } + + static Set populatedSet(int n) { + Set a = ConcurrentHashMap.newKeySet(); + assertTrue(a.isEmpty()); + for (int i = 0; i < n; i++) + assertTrue(a.add(i)); + assertEquals(n == 0, a.isEmpty()); + assertEquals(n, a.size()); + return a; + } + + static Set populatedSet(Integer[] elements) { + Set a = ConcurrentHashMap.newKeySet(); + assertTrue(a.isEmpty()); + for (int i = 0; i < elements.length; i++) + assertTrue(a.add(elements[i])); + assertFalse(a.isEmpty()); + assertEquals(elements.length, a.size()); + return a; + } + + /** + * replaceAll replaces all matching values. + */ + public void testReplaceAll() { + ConcurrentHashMap map = map5(); + map.replaceAll((x, y) -> { return x > 3 ? "Z" : y; }); + assertEquals("A", map.get(one)); + assertEquals("B", map.get(two)); + assertEquals("C", map.get(three)); + assertEquals("Z", map.get(four)); + assertEquals("Z", map.get(five)); + } + + /** + * Default-constructed set is empty + */ + public void testNewKeySet() { + Set a = ConcurrentHashMap.newKeySet(); + assertTrue(a.isEmpty()); + } + + /** + * keySet.add adds the key with the established value to the map; + * remove removes it. + */ + public void testKeySetAddRemove() { + ConcurrentHashMap map = map5(); + Set set1 = map.keySet(); + Set set2 = map.keySet(true); + set2.add(six); + assertTrue(((ConcurrentHashMap.KeySetView)set2).getMap() == map); + assertTrue(((ConcurrentHashMap.KeySetView)set1).getMap() == map); + assertEquals(set2.size(), map.size()); + assertEquals(set1.size(), map.size()); + assertTrue((Boolean)map.get(six)); + assertTrue(set1.contains(six)); + assertTrue(set2.contains(six)); + set2.remove(six); + assertNull(map.get(six)); + assertFalse(set1.contains(six)); + assertFalse(set2.contains(six)); + } + + /** + * keySet.addAll adds each element from the given collection + */ + public void testAddAll() { + Set full = populatedSet(3); + assertTrue(full.addAll(Arrays.asList(three, four, five))); + assertEquals(6, full.size()); + assertFalse(full.addAll(Arrays.asList(three, four, five))); + assertEquals(6, full.size()); + } + + /** + * keySet.addAll adds each element from the given collection that did not + * already exist in the set + */ + public void testAddAll2() { + Set full = populatedSet(3); + // "one" is duplicate and will not be added + assertTrue(full.addAll(Arrays.asList(three, four, one))); + assertEquals(5, full.size()); + assertFalse(full.addAll(Arrays.asList(three, four, one))); + assertEquals(5, full.size()); + } + + /** + * keySet.add will not add the element if it already exists in the set + */ + public void testAdd2() { + Set full = populatedSet(3); + assertFalse(full.add(one)); + assertEquals(3, full.size()); + } + + /** + * keySet.add adds the element when it does not exist in the set + */ + public void testAdd3() { + Set full = populatedSet(3); + assertTrue(full.add(three)); + assertTrue(full.contains(three)); + assertFalse(full.add(three)); + assertTrue(full.contains(three)); + } + + /** + * keySet.add throws UnsupportedOperationException if no default + * mapped value + */ + public void testAdd4() { + Set full = map5().keySet(); + try { + full.add(three); + shouldThrow(); + } catch (UnsupportedOperationException success) {} + } + + /** + * keySet.add throws NullPointerException if the specified key is + * null + */ + public void testAdd5() { + Set full = populatedSet(3); + try { + full.add(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * KeySetView.getMappedValue returns the map's mapped value + */ + public void testGetMappedValue() { + ConcurrentHashMap map = map5(); + assertNull(map.keySet().getMappedValue()); + try { + map.keySet(null); + shouldThrow(); + } catch (NullPointerException success) {} + ConcurrentHashMap.KeySetView set = map.keySet(one); + assertFalse(set.add(one)); + assertTrue(set.add(six)); + assertTrue(set.add(seven)); + assertTrue(set.getMappedValue() == one); + assertTrue(map.get(one) != one); + assertTrue(map.get(six) == one); + assertTrue(map.get(seven) == one); + } + + void checkSpliteratorCharacteristics(Spliterator sp, + int requiredCharacteristics) { + assertEquals(requiredCharacteristics, + requiredCharacteristics & sp.characteristics()); + } + + /** + * KeySetView.spliterator returns spliterator over the elements in this set + */ + public void testKeySetSpliterator() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap map = map5(); + Set set = map.keySet(); + Spliterator sp = set.spliterator(); + checkSpliteratorCharacteristics(sp, CONCURRENT | DISTINCT | NONNULL); + assertEquals(sp.estimateSize(), map.size()); + Spliterator sp2 = sp.trySplit(); + sp.forEachRemaining((Integer x) -> adder.add(x.longValue())); + long v = adder.sumThenReset(); + sp2.forEachRemaining((Integer x) -> adder.add(x.longValue())); + long v2 = adder.sum(); + assertEquals(v + v2, 15); + } + + /** + * keyset.clear removes all elements from the set + */ + public void testClear() { + Set full = populatedSet(3); + full.clear(); + assertEquals(0, full.size()); + } + + /** + * keyset.contains returns true for added elements + */ + public void testContains() { + Set full = populatedSet(3); + assertTrue(full.contains(one)); + assertFalse(full.contains(five)); + } + + /** + * KeySets with equal elements are equal + */ + public void testEquals() { + Set a = populatedSet(3); + Set b = populatedSet(3); + assertTrue(a.equals(b)); + assertTrue(b.equals(a)); + assertEquals(a.hashCode(), b.hashCode()); + a.add(m1); + assertFalse(a.equals(b)); + assertFalse(b.equals(a)); + b.add(m1); + assertTrue(a.equals(b)); + assertTrue(b.equals(a)); + assertEquals(a.hashCode(), b.hashCode()); + } + + /** + * KeySet.containsAll returns true for collections with subset of elements + */ + public void testContainsAll() { + Collection full = populatedSet(3); + assertTrue(full.containsAll(Arrays.asList())); + assertTrue(full.containsAll(Arrays.asList(one))); + assertTrue(full.containsAll(Arrays.asList(one, two))); + assertFalse(full.containsAll(Arrays.asList(one, two, six))); + assertFalse(full.containsAll(Arrays.asList(six))); + } + + /** + * KeySet.isEmpty is true when empty, else false + */ + public void testIsEmpty() { + assertTrue(populatedSet(0).isEmpty()); + assertFalse(populatedSet(3).isEmpty()); + } + + /** + * KeySet.iterator() returns an iterator containing the elements of the + * set + */ + public void testIterator() { + Collection empty = ConcurrentHashMap.newKeySet(); + int size = 20; + assertFalse(empty.iterator().hasNext()); + try { + empty.iterator().next(); + shouldThrow(); + } catch (NoSuchElementException success) {} + + Integer[] elements = new Integer[size]; + for (int i = 0; i < size; i++) + elements[i] = i; + Collections.shuffle(Arrays.asList(elements)); + Collection full = populatedSet(elements); + + Iterator it = full.iterator(); + for (int j = 0; j < size; j++) { + assertTrue(it.hasNext()); + it.next(); + } + assertIteratorExhausted(it); + } + + /** + * iterator of empty collections has no elements + */ + public void testEmptyIterator() { + assertIteratorExhausted(ConcurrentHashMap.newKeySet().iterator()); + assertIteratorExhausted(new ConcurrentHashMap().entrySet().iterator()); + assertIteratorExhausted(new ConcurrentHashMap().values().iterator()); + assertIteratorExhausted(new ConcurrentHashMap().keySet().iterator()); + } + + /** + * KeySet.iterator.remove removes current element + */ + public void testIteratorRemove() { + Set q = populatedSet(3); + Iterator it = q.iterator(); + Object removed = it.next(); + it.remove(); + + it = q.iterator(); + assertFalse(it.next().equals(removed)); + assertFalse(it.next().equals(removed)); + assertFalse(it.hasNext()); + } + + /** + * KeySet.toString holds toString of elements + */ + public void testToString() { + assertEquals("[]", ConcurrentHashMap.newKeySet().toString()); + Set full = populatedSet(3); + String s = full.toString(); + for (int i = 0; i < 3; ++i) + assertTrue(s.contains(String.valueOf(i))); + } + + /** + * KeySet.removeAll removes all elements from the given collection + */ + public void testRemoveAll() { + Set full = populatedSet(3); + assertTrue(full.removeAll(Arrays.asList(one, two))); + assertEquals(1, full.size()); + assertFalse(full.removeAll(Arrays.asList(one, two))); + assertEquals(1, full.size()); + } + + /** + * KeySet.remove removes an element + */ + public void testRemove() { + Set full = populatedSet(3); + full.remove(one); + assertFalse(full.contains(one)); + assertEquals(2, full.size()); + } + + /** + * keySet.size returns the number of elements + */ + public void testSize() { + Set empty = ConcurrentHashMap.newKeySet(); + Set full = populatedSet(3); + assertEquals(3, full.size()); + assertEquals(0, empty.size()); + } + + /** + * KeySet.toArray() returns an Object array containing all elements from + * the set + */ + public void testToArray() { + Object[] a = ConcurrentHashMap.newKeySet().toArray(); + assertTrue(Arrays.equals(new Object[0], a)); + assertSame(Object[].class, a.getClass()); + int size = 20; + Integer[] elements = new Integer[size]; + for (int i = 0; i < size; i++) + elements[i] = i; + Collections.shuffle(Arrays.asList(elements)); + Collection full = populatedSet(elements); + + assertTrue(Arrays.asList(elements).containsAll(Arrays.asList(full.toArray()))); + assertTrue(full.containsAll(Arrays.asList(full.toArray()))); + assertSame(Object[].class, full.toArray().getClass()); + } + + /** + * toArray(Integer array) returns an Integer array containing all + * elements from the set + */ + public void testToArray2() { + Collection empty = ConcurrentHashMap.newKeySet(); + Integer[] a; + int size = 20; + + a = new Integer[0]; + assertSame(a, empty.toArray(a)); + + a = new Integer[size / 2]; + Arrays.fill(a, 42); + assertSame(a, empty.toArray(a)); + assertNull(a[0]); + for (int i = 1; i < a.length; i++) + assertEquals(42, (int) a[i]); + + Integer[] elements = new Integer[size]; + for (int i = 0; i < size; i++) + elements[i] = i; + Collections.shuffle(Arrays.asList(elements)); + Collection full = populatedSet(elements); + + Arrays.fill(a, 42); + assertTrue(Arrays.asList(elements).containsAll(Arrays.asList(full.toArray(a)))); + for (int i = 0; i < a.length; i++) + assertEquals(42, (int) a[i]); + assertSame(Integer[].class, full.toArray(a).getClass()); + + a = new Integer[size]; + Arrays.fill(a, 42); + assertSame(a, full.toArray(a)); + assertTrue(Arrays.asList(elements).containsAll(Arrays.asList(full.toArray(a)))); + } + + /** + * A deserialized serialized set is equal + */ + public void testSerialization() throws Exception { + int size = 20; + Set x = populatedSet(size); + Set y = serialClone(x); + + assertNotSame(x, y); + assertEquals(x.size(), y.size()); + assertEquals(x, y); + assertEquals(y, x); + } + + static final int SIZE = 10000; + static ConcurrentHashMap longMap; + + static ConcurrentHashMap longMap() { + if (longMap == null) { + longMap = new ConcurrentHashMap(SIZE); + for (int i = 0; i < SIZE; ++i) + longMap.put(Long.valueOf(i), Long.valueOf(2 *i)); + } + return longMap; + } + + // explicit function class to avoid type inference problems + static class AddKeys implements BiFunction, Map.Entry, Map.Entry> { + public Map.Entry apply(Map.Entry x, Map.Entry y) { + return new AbstractMap.SimpleEntry + (Long.valueOf(x.getKey().longValue() + y.getKey().longValue()), + Long.valueOf(1L)); + } + } + + /** + * forEachKeySequentially traverses all keys + */ + public void testForEachKeySequentially() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEachKey(Long.MAX_VALUE, (Long x) -> adder.add(x.longValue())); + assertEquals(adder.sum(), SIZE * (SIZE - 1) / 2); + } + + /** + * forEachValueSequentially traverses all values + */ + public void testForEachValueSequentially() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEachValue(Long.MAX_VALUE, (Long x) -> adder.add(x.longValue())); + assertEquals(adder.sum(), SIZE * (SIZE - 1)); + } + + /** + * forEachSequentially traverses all mappings + */ + public void testForEachSequentially() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEach(Long.MAX_VALUE, (Long x, Long y) -> adder.add(x.longValue() + y.longValue())); + assertEquals(adder.sum(), 3 * SIZE * (SIZE - 1) / 2); + } + + /** + * forEachEntrySequentially traverses all entries + */ + public void testForEachEntrySequentially() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEachEntry(Long.MAX_VALUE, (Map.Entry e) -> adder.add(e.getKey().longValue() + e.getValue().longValue())); + assertEquals(adder.sum(), 3 * SIZE * (SIZE - 1) / 2); + } + + /** + * forEachKeyInParallel traverses all keys + */ + public void testForEachKeyInParallel() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEachKey(1L, (Long x) -> adder.add(x.longValue())); + assertEquals(adder.sum(), SIZE * (SIZE - 1) / 2); + } + + /** + * forEachValueInParallel traverses all values + */ + public void testForEachValueInParallel() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEachValue(1L, (Long x) -> adder.add(x.longValue())); + assertEquals(adder.sum(), SIZE * (SIZE - 1)); + } + + /** + * forEachInParallel traverses all mappings + */ + public void testForEachInParallel() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEach(1L, (Long x, Long y) -> adder.add(x.longValue() + y.longValue())); + assertEquals(adder.sum(), 3 * SIZE * (SIZE - 1) / 2); + } + + /** + * forEachEntryInParallel traverses all entries + */ + public void testForEachEntryInParallel() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEachEntry(1L, (Map.Entry e) -> adder.add(e.getKey().longValue() + e.getValue().longValue())); + assertEquals(adder.sum(), 3 * SIZE * (SIZE - 1) / 2); + } + + /** + * Mapped forEachKeySequentially traverses the given + * transformations of all keys + */ + public void testMappedForEachKeySequentially() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEachKey(Long.MAX_VALUE, (Long x) -> Long.valueOf(4 * x.longValue()), + (Long x) -> adder.add(x.longValue())); + assertEquals(adder.sum(), 4 * SIZE * (SIZE - 1) / 2); + } + + /** + * Mapped forEachValueSequentially traverses the given + * transformations of all values + */ + public void testMappedForEachValueSequentially() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEachValue(Long.MAX_VALUE, (Long x) -> Long.valueOf(4 * x.longValue()), + (Long x) -> adder.add(x.longValue())); + assertEquals(adder.sum(), 4 * SIZE * (SIZE - 1)); + } + + /** + * Mapped forEachSequentially traverses the given + * transformations of all mappings + */ + public void testMappedForEachSequentially() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEach(Long.MAX_VALUE, (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue()), + (Long x) -> adder.add(x.longValue())); + assertEquals(adder.sum(), 3 * SIZE * (SIZE - 1) / 2); + } + + /** + * Mapped forEachEntrySequentially traverses the given + * transformations of all entries + */ + public void testMappedForEachEntrySequentially() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEachEntry(Long.MAX_VALUE, (Map.Entry e) -> Long.valueOf(e.getKey().longValue() + e.getValue().longValue()), + (Long x) -> adder.add(x.longValue())); + assertEquals(adder.sum(), 3 * SIZE * (SIZE - 1) / 2); + } + + /** + * Mapped forEachKeyInParallel traverses the given + * transformations of all keys + */ + public void testMappedForEachKeyInParallel() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEachKey(1L, (Long x) -> Long.valueOf(4 * x.longValue()), + (Long x) -> adder.add(x.longValue())); + assertEquals(adder.sum(), 4 * SIZE * (SIZE - 1) / 2); + } + + /** + * Mapped forEachValueInParallel traverses the given + * transformations of all values + */ + public void testMappedForEachValueInParallel() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEachValue(1L, (Long x) -> Long.valueOf(4 * x.longValue()), + (Long x) -> adder.add(x.longValue())); + assertEquals(adder.sum(), 4 * SIZE * (SIZE - 1)); + } + + /** + * Mapped forEachInParallel traverses the given + * transformations of all mappings + */ + public void testMappedForEachInParallel() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEach(1L, (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue()), + (Long x) -> adder.add(x.longValue())); + assertEquals(adder.sum(), 3 * SIZE * (SIZE - 1) / 2); + } + + /** + * Mapped forEachEntryInParallel traverses the given + * transformations of all entries + */ + public void testMappedForEachEntryInParallel() { + LongAdder adder = new LongAdder(); + ConcurrentHashMap m = longMap(); + m.forEachEntry(1L, (Map.Entry e) -> Long.valueOf(e.getKey().longValue() + e.getValue().longValue()), + (Long x) -> adder.add(x.longValue())); + assertEquals(adder.sum(), 3 * SIZE * (SIZE - 1) / 2); + } + + /** + * reduceKeysSequentially accumulates across all keys, + */ + public void testReduceKeysSequentially() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.reduceKeys(Long.MAX_VALUE, (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue())); + assertEquals((long)r, (long)SIZE * (SIZE - 1) / 2); + } + + /** + * reduceValuesSequentially accumulates across all values + */ + public void testReduceValuesSequentially() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.reduceKeys(Long.MAX_VALUE, (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue())); + assertEquals((long)r, (long)SIZE * (SIZE - 1) / 2); + } + + /** + * reduceEntriesSequentially accumulates across all entries + */ + public void testReduceEntriesSequentially() { + ConcurrentHashMap m = longMap(); + Map.Entry r; + r = m.reduceEntries(Long.MAX_VALUE, new AddKeys()); + assertEquals(r.getKey().longValue(), (long)SIZE * (SIZE - 1) / 2); + } + + /** + * reduceKeysInParallel accumulates across all keys + */ + public void testReduceKeysInParallel() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.reduceKeys(1L, (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue())); + assertEquals((long)r, (long)SIZE * (SIZE - 1) / 2); + } + + /** + * reduceValuesInParallel accumulates across all values + */ + public void testReduceValuesInParallel() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.reduceValues(1L, (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue())); + assertEquals((long)r, (long)SIZE * (SIZE - 1)); + } + + /** + * reduceEntriesInParallel accumulate across all entries + */ + public void testReduceEntriesInParallel() { + ConcurrentHashMap m = longMap(); + Map.Entry r; + r = m.reduceEntries(1L, new AddKeys()); + assertEquals(r.getKey().longValue(), (long)SIZE * (SIZE - 1) / 2); + } + + /** + * Mapped reduceKeysSequentially accumulates mapped keys + */ + public void testMapReduceKeysSequentially() { + ConcurrentHashMap m = longMap(); + Long r = m.reduceKeys(Long.MAX_VALUE, (Long x) -> Long.valueOf(4 * x.longValue()), + (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue())); + assertEquals((long)r, (long)4 * SIZE * (SIZE - 1) / 2); + } + + /** + * Mapped reduceValuesSequentially accumulates mapped values + */ + public void testMapReduceValuesSequentially() { + ConcurrentHashMap m = longMap(); + Long r = m.reduceValues(Long.MAX_VALUE, (Long x) -> Long.valueOf(4 * x.longValue()), + (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue())); + assertEquals((long)r, (long)4 * SIZE * (SIZE - 1)); + } + + /** + * reduceSequentially accumulates across all transformed mappings + */ + public void testMappedReduceSequentially() { + ConcurrentHashMap m = longMap(); + Long r = m.reduce(Long.MAX_VALUE, (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue()), + (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue())); + + assertEquals((long)r, (long)3 * SIZE * (SIZE - 1) / 2); + } + + /** + * Mapped reduceKeysInParallel, accumulates mapped keys + */ + public void testMapReduceKeysInParallel() { + ConcurrentHashMap m = longMap(); + Long r = m.reduceKeys(1L, (Long x) -> Long.valueOf(4 * x.longValue()), + (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue())); + assertEquals((long)r, (long)4 * SIZE * (SIZE - 1) / 2); + } + + /** + * Mapped reduceValuesInParallel accumulates mapped values + */ + public void testMapReduceValuesInParallel() { + ConcurrentHashMap m = longMap(); + Long r = m.reduceValues(1L, (Long x) -> Long.valueOf(4 * x.longValue()), + (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue())); + assertEquals((long)r, (long)4 * SIZE * (SIZE - 1)); + } + + /** + * reduceInParallel accumulate across all transformed mappings + */ + public void testMappedReduceInParallel() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.reduce(1L, (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue()), + (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue())); + assertEquals((long)r, (long)3 * SIZE * (SIZE - 1) / 2); + } + + /** + * reduceKeysToLongSequentially accumulates mapped keys + */ + public void testReduceKeysToLongSequentially() { + ConcurrentHashMap m = longMap(); + long lr = m.reduceKeysToLong(Long.MAX_VALUE, (Long x) -> x.longValue(), 0L, Long::sum); + assertEquals(lr, (long)SIZE * (SIZE - 1) / 2); + } + + /** + * reduceKeysToIntSequentially accumulates mapped keys + */ + public void testReduceKeysToIntSequentially() { + ConcurrentHashMap m = longMap(); + int ir = m.reduceKeysToInt(Long.MAX_VALUE, (Long x) -> x.intValue(), 0, Integer::sum); + assertEquals(ir, SIZE * (SIZE - 1) / 2); + } + + /** + * reduceKeysToDoubleSequentially accumulates mapped keys + */ + public void testReduceKeysToDoubleSequentially() { + ConcurrentHashMap m = longMap(); + double dr = m.reduceKeysToDouble(Long.MAX_VALUE, (Long x) -> x.doubleValue(), 0.0, Double::sum); + assertEquals(dr, (double)SIZE * (SIZE - 1) / 2); + } + + /** + * reduceValuesToLongSequentially accumulates mapped values + */ + public void testReduceValuesToLongSequentially() { + ConcurrentHashMap m = longMap(); + long lr = m.reduceValuesToLong(Long.MAX_VALUE, (Long x) -> x.longValue(), 0L, Long::sum); + assertEquals(lr, (long)SIZE * (SIZE - 1)); + } + + /** + * reduceValuesToIntSequentially accumulates mapped values + */ + public void testReduceValuesToIntSequentially() { + ConcurrentHashMap m = longMap(); + int ir = m.reduceValuesToInt(Long.MAX_VALUE, (Long x) -> x.intValue(), 0, Integer::sum); + assertEquals(ir, SIZE * (SIZE - 1)); + } + + /** + * reduceValuesToDoubleSequentially accumulates mapped values + */ + public void testReduceValuesToDoubleSequentially() { + ConcurrentHashMap m = longMap(); + double dr = m.reduceValuesToDouble(Long.MAX_VALUE, (Long x) -> x.doubleValue(), 0.0, Double::sum); + assertEquals(dr, (double)SIZE * (SIZE - 1)); + } + + /** + * reduceKeysToLongInParallel accumulates mapped keys + */ + public void testReduceKeysToLongInParallel() { + ConcurrentHashMap m = longMap(); + long lr = m.reduceKeysToLong(1L, (Long x) -> x.longValue(), 0L, Long::sum); + assertEquals(lr, (long)SIZE * (SIZE - 1) / 2); + } + + /** + * reduceKeysToIntInParallel accumulates mapped keys + */ + public void testReduceKeysToIntInParallel() { + ConcurrentHashMap m = longMap(); + int ir = m.reduceKeysToInt(1L, (Long x) -> x.intValue(), 0, Integer::sum); + assertEquals(ir, SIZE * (SIZE - 1) / 2); + } + + /** + * reduceKeysToDoubleInParallel accumulates mapped values + */ + public void testReduceKeysToDoubleInParallel() { + ConcurrentHashMap m = longMap(); + double dr = m.reduceKeysToDouble(1L, (Long x) -> x.doubleValue(), 0.0, Double::sum); + assertEquals(dr, (double)SIZE * (SIZE - 1) / 2); + } + + /** + * reduceValuesToLongInParallel accumulates mapped values + */ + public void testReduceValuesToLongInParallel() { + ConcurrentHashMap m = longMap(); + long lr = m.reduceValuesToLong(1L, (Long x) -> x.longValue(), 0L, Long::sum); + assertEquals(lr, (long)SIZE * (SIZE - 1)); + } + + /** + * reduceValuesToIntInParallel accumulates mapped values + */ + public void testReduceValuesToIntInParallel() { + ConcurrentHashMap m = longMap(); + int ir = m.reduceValuesToInt(1L, (Long x) -> x.intValue(), 0, Integer::sum); + assertEquals(ir, SIZE * (SIZE - 1)); + } + + /** + * reduceValuesToDoubleInParallel accumulates mapped values + */ + public void testReduceValuesToDoubleInParallel() { + ConcurrentHashMap m = longMap(); + double dr = m.reduceValuesToDouble(1L, (Long x) -> x.doubleValue(), 0.0, Double::sum); + assertEquals(dr, (double)SIZE * (SIZE - 1)); + } + + /** + * searchKeysSequentially returns a non-null result of search + * function, or null if none + */ + public void testSearchKeysSequentially() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.searchKeys(Long.MAX_VALUE, (Long x) -> x.longValue() == (long)(SIZE/2) ? x : null); + assertEquals((long)r, (long)(SIZE/2)); + r = m.searchKeys(Long.MAX_VALUE, (Long x) -> x.longValue() < 0L ? x : null); + assertNull(r); + } + + /** + * searchValuesSequentially returns a non-null result of search + * function, or null if none + */ + public void testSearchValuesSequentially() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.searchValues(Long.MAX_VALUE, + (Long x) -> (x.longValue() == (long)(SIZE/2)) ? x : null); + assertEquals((long)r, (long)(SIZE/2)); + r = m.searchValues(Long.MAX_VALUE, + (Long x) -> (x.longValue() < 0L) ? x : null); + assertNull(r); + } + + /** + * searchSequentially returns a non-null result of search + * function, or null if none + */ + public void testSearchSequentially() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.search(Long.MAX_VALUE, (Long x, Long y) -> x.longValue() == (long)(SIZE/2) ? x : null); + assertEquals((long)r, (long)(SIZE/2)); + r = m.search(Long.MAX_VALUE, (Long x, Long y) -> x.longValue() < 0L ? x : null); + assertNull(r); + } + + /** + * searchEntriesSequentially returns a non-null result of search + * function, or null if none + */ + public void testSearchEntriesSequentially() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.searchEntries(Long.MAX_VALUE, (Map.Entry e) -> e.getKey().longValue() == (long)(SIZE/2) ? e.getKey() : null); + assertEquals((long)r, (long)(SIZE/2)); + r = m.searchEntries(Long.MAX_VALUE, (Map.Entry e) -> e.getKey().longValue() < 0L ? e.getKey() : null); + assertNull(r); + } + + /** + * searchKeysInParallel returns a non-null result of search + * function, or null if none + */ + public void testSearchKeysInParallel() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.searchKeys(1L, (Long x) -> x.longValue() == (long)(SIZE/2) ? x : null); + assertEquals((long)r, (long)(SIZE/2)); + r = m.searchKeys(1L, (Long x) -> x.longValue() < 0L ? x : null); + assertNull(r); + } + + /** + * searchValuesInParallel returns a non-null result of search + * function, or null if none + */ + public void testSearchValuesInParallel() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.searchValues(1L, (Long x) -> x.longValue() == (long)(SIZE/2) ? x : null); + assertEquals((long)r, (long)(SIZE/2)); + r = m.searchValues(1L, (Long x) -> x.longValue() < 0L ? x : null); + assertNull(r); + } + + /** + * searchInParallel returns a non-null result of search function, + * or null if none + */ + public void testSearchInParallel() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.search(1L, (Long x, Long y) -> x.longValue() == (long)(SIZE/2) ? x : null); + assertEquals((long)r, (long)(SIZE/2)); + r = m.search(1L, (Long x, Long y) -> x.longValue() < 0L ? x : null); + assertNull(r); + } + + /** + * searchEntriesInParallel returns a non-null result of search + * function, or null if none + */ + public void testSearchEntriesInParallel() { + ConcurrentHashMap m = longMap(); + Long r; + r = m.searchEntries(1L, (Map.Entry e) -> e.getKey().longValue() == (long)(SIZE/2) ? e.getKey() : null); + assertEquals((long)r, (long)(SIZE/2)); + r = m.searchEntries(1L, (Map.Entry e) -> e.getKey().longValue() < 0L ? e.getKey() : null); + assertNull(r); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/ConcurrentHashMapTest.java b/jdk/test/java/util/concurrent/tck/ConcurrentHashMapTest.java new file mode 100644 index 00000000000..766511902af --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ConcurrentHashMapTest.java @@ -0,0 +1,833 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ConcurrentHashMapTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(ConcurrentHashMapTest.class); + } + + /** + * Returns a new map from Integers 1-5 to Strings "A"-"E". + */ + private static ConcurrentHashMap map5() { + ConcurrentHashMap map = new ConcurrentHashMap(5); + assertTrue(map.isEmpty()); + map.put(one, "A"); + map.put(two, "B"); + map.put(three, "C"); + map.put(four, "D"); + map.put(five, "E"); + assertFalse(map.isEmpty()); + assertEquals(5, map.size()); + return map; + } + + /** Re-implement Integer.compare for old java versions */ + static int compare(int x, int y) { + return (x < y) ? -1 : (x > y) ? 1 : 0; + } + + // classes for testing Comparable fallbacks + static class BI implements Comparable { + private final int value; + BI(int value) { this.value = value; } + public int compareTo(BI other) { + return compare(value, other.value); + } + public boolean equals(Object x) { + return (x instanceof BI) && ((BI)x).value == value; + } + public int hashCode() { return 42; } + } + static class CI extends BI { CI(int value) { super(value); } } + static class DI extends BI { DI(int value) { super(value); } } + + static class BS implements Comparable { + private final String value; + BS(String value) { this.value = value; } + public int compareTo(BS other) { + return value.compareTo(other.value); + } + public boolean equals(Object x) { + return (x instanceof BS) && value.equals(((BS)x).value); + } + public int hashCode() { return 42; } + } + + static class LexicographicList> extends ArrayList + implements Comparable> { + LexicographicList(Collection c) { super(c); } + LexicographicList(E e) { super(Collections.singleton(e)); } + public int compareTo(LexicographicList other) { + int common = Math.min(size(), other.size()); + int r = 0; + for (int i = 0; i < common; i++) { + if ((r = get(i).compareTo(other.get(i))) != 0) + break; + } + if (r == 0) + r = compare(size(), other.size()); + return r; + } + private static final long serialVersionUID = 0; + } + + static class CollidingObject { + final String value; + CollidingObject(final String value) { this.value = value; } + public int hashCode() { return this.value.hashCode() & 1; } + public boolean equals(final Object obj) { + return (obj instanceof CollidingObject) && ((CollidingObject)obj).value.equals(value); + } + } + + static class ComparableCollidingObject extends CollidingObject implements Comparable { + ComparableCollidingObject(final String value) { super(value); } + public int compareTo(final ComparableCollidingObject o) { + return value.compareTo(o.value); + } + } + + /** + * Inserted elements that are subclasses of the same Comparable + * class are found. + */ + public void testComparableFamily() { + int size = 500; // makes measured test run time -> 60ms + ConcurrentHashMap m = + new ConcurrentHashMap(); + for (int i = 0; i < size; i++) { + assertTrue(m.put(new CI(i), true) == null); + } + for (int i = 0; i < size; i++) { + assertTrue(m.containsKey(new CI(i))); + assertTrue(m.containsKey(new DI(i))); + } + } + + /** + * Elements of classes with erased generic type parameters based + * on Comparable can be inserted and found. + */ + public void testGenericComparable() { + int size = 120; // makes measured test run time -> 60ms + ConcurrentHashMap m = + new ConcurrentHashMap(); + for (int i = 0; i < size; i++) { + BI bi = new BI(i); + BS bs = new BS(String.valueOf(i)); + LexicographicList bis = new LexicographicList(bi); + LexicographicList bss = new LexicographicList(bs); + assertTrue(m.putIfAbsent(bis, true) == null); + assertTrue(m.containsKey(bis)); + if (m.putIfAbsent(bss, true) == null) + assertTrue(m.containsKey(bss)); + assertTrue(m.containsKey(bis)); + } + for (int i = 0; i < size; i++) { + assertTrue(m.containsKey(Collections.singletonList(new BI(i)))); + } + } + + /** + * Elements of non-comparable classes equal to those of classes + * with erased generic type parameters based on Comparable can be + * inserted and found. + */ + public void testGenericComparable2() { + int size = 500; // makes measured test run time -> 60ms + ConcurrentHashMap m = + new ConcurrentHashMap(); + for (int i = 0; i < size; i++) { + m.put(Collections.singletonList(new BI(i)), true); + } + + for (int i = 0; i < size; i++) { + LexicographicList bis = new LexicographicList(new BI(i)); + assertTrue(m.containsKey(bis)); + } + } + + /** + * Mixtures of instances of comparable and non-comparable classes + * can be inserted and found. + */ + public void testMixedComparable() { + int size = 1200; // makes measured test run time -> 35ms + ConcurrentHashMap map = + new ConcurrentHashMap(); + Random rng = new Random(); + for (int i = 0; i < size; i++) { + Object x; + switch (rng.nextInt(4)) { + case 0: + x = new Object(); + break; + case 1: + x = new CollidingObject(Integer.toString(i)); + break; + default: + x = new ComparableCollidingObject(Integer.toString(i)); + } + assertNull(map.put(x, x)); + } + int count = 0; + for (Object k : map.keySet()) { + assertEquals(map.get(k), k); + ++count; + } + assertEquals(count, size); + assertEquals(map.size(), size); + for (Object k : map.keySet()) { + assertEquals(map.put(k, k), k); + } + } + + /** + * clear removes all pairs + */ + public void testClear() { + ConcurrentHashMap map = map5(); + map.clear(); + assertEquals(0, map.size()); + } + + /** + * Maps with same contents are equal + */ + public void testEquals() { + ConcurrentHashMap map1 = map5(); + ConcurrentHashMap map2 = map5(); + assertEquals(map1, map2); + assertEquals(map2, map1); + map1.clear(); + assertFalse(map1.equals(map2)); + assertFalse(map2.equals(map1)); + } + + /** + * hashCode() equals sum of each key.hashCode ^ value.hashCode + */ + public void testHashCode() { + ConcurrentHashMap map = map5(); + int sum = 0; + for (Map.Entry e : map.entrySet()) + sum += e.getKey().hashCode() ^ e.getValue().hashCode(); + assertEquals(sum, map.hashCode()); + } + + /** + * contains returns true for contained value + */ + public void testContains() { + ConcurrentHashMap map = map5(); + assertTrue(map.contains("A")); + assertFalse(map.contains("Z")); + } + + /** + * containsKey returns true for contained key + */ + public void testContainsKey() { + ConcurrentHashMap map = map5(); + assertTrue(map.containsKey(one)); + assertFalse(map.containsKey(zero)); + } + + /** + * containsValue returns true for held values + */ + public void testContainsValue() { + ConcurrentHashMap map = map5(); + assertTrue(map.containsValue("A")); + assertFalse(map.containsValue("Z")); + } + + /** + * enumeration returns an enumeration containing the correct + * elements + */ + public void testEnumeration() { + ConcurrentHashMap map = map5(); + Enumeration e = map.elements(); + int count = 0; + while (e.hasMoreElements()) { + count++; + e.nextElement(); + } + assertEquals(5, count); + } + + /** + * get returns the correct element at the given key, + * or null if not present + */ + public void testGet() { + ConcurrentHashMap map = map5(); + assertEquals("A", (String)map.get(one)); + ConcurrentHashMap empty = new ConcurrentHashMap(); + assertNull(map.get("anything")); + assertNull(empty.get("anything")); + } + + /** + * isEmpty is true of empty map and false for non-empty + */ + public void testIsEmpty() { + ConcurrentHashMap empty = new ConcurrentHashMap(); + ConcurrentHashMap map = map5(); + assertTrue(empty.isEmpty()); + assertFalse(map.isEmpty()); + } + + /** + * keys returns an enumeration containing all the keys from the map + */ + public void testKeys() { + ConcurrentHashMap map = map5(); + Enumeration e = map.keys(); + int count = 0; + while (e.hasMoreElements()) { + count++; + e.nextElement(); + } + assertEquals(5, count); + } + + /** + * keySet returns a Set containing all the keys + */ + public void testKeySet() { + ConcurrentHashMap map = map5(); + Set s = map.keySet(); + assertEquals(5, s.size()); + assertTrue(s.contains(one)); + assertTrue(s.contains(two)); + assertTrue(s.contains(three)); + assertTrue(s.contains(four)); + assertTrue(s.contains(five)); + } + + /** + * keySet.toArray returns contains all keys + */ + public void testKeySetToArray() { + ConcurrentHashMap map = map5(); + Set s = map.keySet(); + Object[] ar = s.toArray(); + assertTrue(s.containsAll(Arrays.asList(ar))); + assertEquals(5, ar.length); + ar[0] = m10; + assertFalse(s.containsAll(Arrays.asList(ar))); + } + + /** + * Values.toArray contains all values + */ + public void testValuesToArray() { + ConcurrentHashMap map = map5(); + Collection v = map.values(); + Object[] ar = v.toArray(); + ArrayList s = new ArrayList(Arrays.asList(ar)); + assertEquals(5, ar.length); + assertTrue(s.contains("A")); + assertTrue(s.contains("B")); + assertTrue(s.contains("C")); + assertTrue(s.contains("D")); + assertTrue(s.contains("E")); + } + + /** + * entrySet.toArray contains all entries + */ + public void testEntrySetToArray() { + ConcurrentHashMap map = map5(); + Set s = map.entrySet(); + Object[] ar = s.toArray(); + assertEquals(5, ar.length); + for (int i = 0; i < 5; ++i) { + assertTrue(map.containsKey(((Map.Entry)(ar[i])).getKey())); + assertTrue(map.containsValue(((Map.Entry)(ar[i])).getValue())); + } + } + + /** + * values collection contains all values + */ + public void testValues() { + ConcurrentHashMap map = map5(); + Collection s = map.values(); + assertEquals(5, s.size()); + assertTrue(s.contains("A")); + assertTrue(s.contains("B")); + assertTrue(s.contains("C")); + assertTrue(s.contains("D")); + assertTrue(s.contains("E")); + } + + /** + * entrySet contains all pairs + */ + public void testEntrySet() { + ConcurrentHashMap map = map5(); + Set s = map.entrySet(); + assertEquals(5, s.size()); + Iterator it = s.iterator(); + while (it.hasNext()) { + Map.Entry e = (Map.Entry) it.next(); + assertTrue( + (e.getKey().equals(one) && e.getValue().equals("A")) || + (e.getKey().equals(two) && e.getValue().equals("B")) || + (e.getKey().equals(three) && e.getValue().equals("C")) || + (e.getKey().equals(four) && e.getValue().equals("D")) || + (e.getKey().equals(five) && e.getValue().equals("E"))); + } + } + + /** + * putAll adds all key-value pairs from the given map + */ + public void testPutAll() { + ConcurrentHashMap empty = new ConcurrentHashMap(); + ConcurrentHashMap map = map5(); + empty.putAll(map); + assertEquals(5, empty.size()); + assertTrue(empty.containsKey(one)); + assertTrue(empty.containsKey(two)); + assertTrue(empty.containsKey(three)); + assertTrue(empty.containsKey(four)); + assertTrue(empty.containsKey(five)); + } + + /** + * putIfAbsent works when the given key is not present + */ + public void testPutIfAbsent() { + ConcurrentHashMap map = map5(); + map.putIfAbsent(six, "Z"); + assertTrue(map.containsKey(six)); + } + + /** + * putIfAbsent does not add the pair if the key is already present + */ + public void testPutIfAbsent2() { + ConcurrentHashMap map = map5(); + assertEquals("A", map.putIfAbsent(one, "Z")); + } + + /** + * replace fails when the given key is not present + */ + public void testReplace() { + ConcurrentHashMap map = map5(); + assertNull(map.replace(six, "Z")); + assertFalse(map.containsKey(six)); + } + + /** + * replace succeeds if the key is already present + */ + public void testReplace2() { + ConcurrentHashMap map = map5(); + assertNotNull(map.replace(one, "Z")); + assertEquals("Z", map.get(one)); + } + + /** + * replace value fails when the given key not mapped to expected value + */ + public void testReplaceValue() { + ConcurrentHashMap map = map5(); + assertEquals("A", map.get(one)); + assertFalse(map.replace(one, "Z", "Z")); + assertEquals("A", map.get(one)); + } + + /** + * replace value succeeds when the given key mapped to expected value + */ + public void testReplaceValue2() { + ConcurrentHashMap map = map5(); + assertEquals("A", map.get(one)); + assertTrue(map.replace(one, "A", "Z")); + assertEquals("Z", map.get(one)); + } + + /** + * remove removes the correct key-value pair from the map + */ + public void testRemove() { + ConcurrentHashMap map = map5(); + map.remove(five); + assertEquals(4, map.size()); + assertFalse(map.containsKey(five)); + } + + /** + * remove(key,value) removes only if pair present + */ + public void testRemove2() { + ConcurrentHashMap map = map5(); + map.remove(five, "E"); + assertEquals(4, map.size()); + assertFalse(map.containsKey(five)); + map.remove(four, "A"); + assertEquals(4, map.size()); + assertTrue(map.containsKey(four)); + } + + /** + * size returns the correct values + */ + public void testSize() { + ConcurrentHashMap map = map5(); + ConcurrentHashMap empty = new ConcurrentHashMap(); + assertEquals(0, empty.size()); + assertEquals(5, map.size()); + } + + /** + * toString contains toString of elements + */ + public void testToString() { + ConcurrentHashMap map = map5(); + String s = map.toString(); + for (int i = 1; i <= 5; ++i) { + assertTrue(s.contains(String.valueOf(i))); + } + } + + // Exception tests + + /** + * Cannot create with only negative capacity + */ + public void testConstructor1() { + try { + new ConcurrentHashMap(-1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor (initialCapacity, loadFactor) throws + * IllegalArgumentException if either argument is negative + */ + public void testConstructor2() { + try { + new ConcurrentHashMap(-1, .75f); + shouldThrow(); + } catch (IllegalArgumentException success) {} + + try { + new ConcurrentHashMap(16, -1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor (initialCapacity, loadFactor, concurrencyLevel) + * throws IllegalArgumentException if any argument is negative + */ + public void testConstructor3() { + try { + new ConcurrentHashMap(-1, .75f, 1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + + try { + new ConcurrentHashMap(16, -1, 1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + + try { + new ConcurrentHashMap(16, .75f, -1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * ConcurrentHashMap(map) throws NullPointerException if the given + * map is null + */ + public void testConstructor4() { + try { + new ConcurrentHashMap(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * ConcurrentHashMap(map) creates a new map with the same mappings + * as the given map + */ + public void testConstructor5() { + ConcurrentHashMap map1 = map5(); + ConcurrentHashMap map2 = new ConcurrentHashMap(map5()); + assertTrue(map2.equals(map1)); + map2.put(one, "F"); + assertFalse(map2.equals(map1)); + } + + /** + * get(null) throws NPE + */ + public void testGet_NullPointerException() { + ConcurrentHashMap c = new ConcurrentHashMap(5); + try { + c.get(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * containsKey(null) throws NPE + */ + public void testContainsKey_NullPointerException() { + ConcurrentHashMap c = new ConcurrentHashMap(5); + try { + c.containsKey(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * containsValue(null) throws NPE + */ + public void testContainsValue_NullPointerException() { + ConcurrentHashMap c = new ConcurrentHashMap(5); + try { + c.containsValue(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * contains(null) throws NPE + */ + public void testContains_NullPointerException() { + ConcurrentHashMap c = new ConcurrentHashMap(5); + try { + c.contains(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * put(null,x) throws NPE + */ + public void testPut1_NullPointerException() { + ConcurrentHashMap c = new ConcurrentHashMap(5); + try { + c.put(null, "whatever"); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * put(x, null) throws NPE + */ + public void testPut2_NullPointerException() { + ConcurrentHashMap c = new ConcurrentHashMap(5); + try { + c.put("whatever", null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * putIfAbsent(null, x) throws NPE + */ + public void testPutIfAbsent1_NullPointerException() { + ConcurrentHashMap c = new ConcurrentHashMap(5); + try { + c.putIfAbsent(null, "whatever"); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * replace(null, x) throws NPE + */ + public void testReplace_NullPointerException() { + ConcurrentHashMap c = new ConcurrentHashMap(5); + try { + c.replace(null, "whatever"); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * replace(null, x, y) throws NPE + */ + public void testReplaceValue_NullPointerException() { + ConcurrentHashMap c = new ConcurrentHashMap(5); + try { + c.replace(null, one, "whatever"); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * putIfAbsent(x, null) throws NPE + */ + public void testPutIfAbsent2_NullPointerException() { + ConcurrentHashMap c = new ConcurrentHashMap(5); + try { + c.putIfAbsent("whatever", null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * replace(x, null) throws NPE + */ + public void testReplace2_NullPointerException() { + ConcurrentHashMap c = new ConcurrentHashMap(5); + try { + c.replace("whatever", null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * replace(x, null, y) throws NPE + */ + public void testReplaceValue2_NullPointerException() { + ConcurrentHashMap c = new ConcurrentHashMap(5); + try { + c.replace("whatever", null, "A"); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * replace(x, y, null) throws NPE + */ + public void testReplaceValue3_NullPointerException() { + ConcurrentHashMap c = new ConcurrentHashMap(5); + try { + c.replace("whatever", one, null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * remove(null) throws NPE + */ + public void testRemove1_NullPointerException() { + ConcurrentHashMap c = new ConcurrentHashMap(5); + c.put("sadsdf", "asdads"); + try { + c.remove(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * remove(null, x) throws NPE + */ + public void testRemove2_NullPointerException() { + ConcurrentHashMap c = new ConcurrentHashMap(5); + c.put("sadsdf", "asdads"); + try { + c.remove(null, "whatever"); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * remove(x, null) returns false + */ + public void testRemove3() { + ConcurrentHashMap c = new ConcurrentHashMap(5); + c.put("sadsdf", "asdads"); + assertFalse(c.remove("sadsdf", null)); + } + + /** + * A deserialized map equals original + */ + public void testSerialization() throws Exception { + Map x = map5(); + Map y = serialClone(x); + + assertNotSame(x, y); + assertEquals(x.size(), y.size()); + assertEquals(x, y); + assertEquals(y, x); + } + + /** + * SetValue of an EntrySet entry sets value in the map. + */ + public void testSetValueWriteThrough() { + // Adapted from a bug report by Eric Zoerner + ConcurrentHashMap map = new ConcurrentHashMap(2, 5.0f, 1); + assertTrue(map.isEmpty()); + for (int i = 0; i < 20; i++) + map.put(new Integer(i), new Integer(i)); + assertFalse(map.isEmpty()); + Map.Entry entry1 = (Map.Entry)map.entrySet().iterator().next(); + // Unless it happens to be first (in which case remainder of + // test is skipped), remove a possibly-colliding key from map + // which, under some implementations, may cause entry1 to be + // cloned in map + if (!entry1.getKey().equals(new Integer(16))) { + map.remove(new Integer(16)); + entry1.setValue("XYZ"); + assertTrue(map.containsValue("XYZ")); // fails if write-through broken + } + } + +} diff --git a/jdk/test/java/util/concurrent/tck/ConcurrentLinkedDequeTest.java b/jdk/test/java/util/concurrent/tck/ConcurrentLinkedDequeTest.java new file mode 100644 index 00000000000..daab5381529 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ConcurrentLinkedDequeTest.java @@ -0,0 +1,926 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import java.util.Arrays; +import java.util.Collection; +import java.util.Deque; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.Random; +import java.util.concurrent.ConcurrentLinkedDeque; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ConcurrentLinkedDequeTest extends JSR166TestCase { + + public static void main(String[] args) { + main(suite(), args); + } + + public static Test suite() { + return new TestSuite(ConcurrentLinkedDequeTest.class); + } + + /** + * Returns a new deque of given size containing consecutive + * Integers 0 ... n. + */ + private ConcurrentLinkedDeque populatedDeque(int n) { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); + assertTrue(q.isEmpty()); + for (int i = 0; i < n; ++i) + assertTrue(q.offer(new Integer(i))); + assertFalse(q.isEmpty()); + assertEquals(n, q.size()); + return q; + } + + /** + * new deque is empty + */ + public void testConstructor1() { + assertTrue(new ConcurrentLinkedDeque().isEmpty()); + assertEquals(0, new ConcurrentLinkedDeque().size()); + } + + /** + * Initializing from null Collection throws NPE + */ + public void testConstructor3() { + try { + new ConcurrentLinkedDeque((Collection)null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing from Collection of null elements throws NPE + */ + public void testConstructor4() { + try { + new ConcurrentLinkedDeque(Arrays.asList(new Integer[SIZE])); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing from Collection with some null elements throws NPE + */ + public void testConstructor5() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); + try { + new ConcurrentLinkedDeque(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Deque contains all elements of collection used to initialize + */ + public void testConstructor6() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(i); + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(Arrays.asList(ints)); + for (int i = 0; i < SIZE; ++i) + assertEquals(ints[i], q.poll()); + } + + /** + * isEmpty is true before add, false after + */ + public void testEmpty() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); + assertTrue(q.isEmpty()); + q.add(one); + assertFalse(q.isEmpty()); + q.add(two); + q.remove(); + q.remove(); + assertTrue(q.isEmpty()); + } + + /** + * size() changes when elements added and removed + */ + public void testSize() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(SIZE - i, q.size()); + q.remove(); + } + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.size()); + q.add(new Integer(i)); + } + } + + /** + * push(null) throws NPE + */ + public void testPushNull() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); + try { + q.push(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * peekFirst() returns element inserted with push + */ + public void testPush() { + ConcurrentLinkedDeque q = populatedDeque(3); + q.pollLast(); + q.push(four); + assertSame(four, q.peekFirst()); + } + + /** + * pop() removes first element, or throws NSEE if empty + */ + public void testPop() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.pop()); + } + try { + q.pop(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * offer(null) throws NPE + */ + public void testOfferNull() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); + try { + q.offer(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * offerFirst(null) throws NPE + */ + public void testOfferFirstNull() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); + try { + q.offerFirst(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * offerLast(null) throws NPE + */ + public void testOfferLastNull() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); + try { + q.offerLast(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * offer(x) succeeds + */ + public void testOffer() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); + assertTrue(q.offer(zero)); + assertTrue(q.offer(one)); + assertSame(zero, q.peekFirst()); + assertSame(one, q.peekLast()); + } + + /** + * offerFirst(x) succeeds + */ + public void testOfferFirst() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); + assertTrue(q.offerFirst(zero)); + assertTrue(q.offerFirst(one)); + assertSame(one, q.peekFirst()); + assertSame(zero, q.peekLast()); + } + + /** + * offerLast(x) succeeds + */ + public void testOfferLast() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); + assertTrue(q.offerLast(zero)); + assertTrue(q.offerLast(one)); + assertSame(zero, q.peekFirst()); + assertSame(one, q.peekLast()); + } + + /** + * add(null) throws NPE + */ + public void testAddNull() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); + try { + q.add(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addFirst(null) throws NPE + */ + public void testAddFirstNull() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); + try { + q.addFirst(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addLast(null) throws NPE + */ + public void testAddLastNull() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); + try { + q.addLast(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * add(x) succeeds + */ + public void testAdd() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); + assertTrue(q.add(zero)); + assertTrue(q.add(one)); + assertSame(zero, q.peekFirst()); + assertSame(one, q.peekLast()); + } + + /** + * addFirst(x) succeeds + */ + public void testAddFirst() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); + q.addFirst(zero); + q.addFirst(one); + assertSame(one, q.peekFirst()); + assertSame(zero, q.peekLast()); + } + + /** + * addLast(x) succeeds + */ + public void testAddLast() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); + q.addLast(zero); + q.addLast(one); + assertSame(zero, q.peekFirst()); + assertSame(one, q.peekLast()); + } + + /** + * addAll(null) throws NPE + */ + public void testAddAll1() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); + try { + q.addAll(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll(this) throws IAE + */ + public void testAddAllSelf() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + try { + q.addAll(q); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * addAll of a collection with null elements throws NPE + */ + public void testAddAll2() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); + try { + q.addAll(Arrays.asList(new Integer[SIZE])); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll of a collection with any null elements throws NPE after + * possibly adding some elements + */ + public void testAddAll3() { + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); + try { + q.addAll(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Deque contains all elements, in traversal order, of successful addAll + */ + public void testAddAll5() { + Integer[] empty = new Integer[0]; + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(i); + ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); + assertFalse(q.addAll(Arrays.asList(empty))); + assertTrue(q.addAll(Arrays.asList(ints))); + for (int i = 0; i < SIZE; ++i) + assertEquals(ints[i], q.poll()); + } + + /** + * pollFirst() succeeds unless empty + */ + public void testPollFirst() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.pollFirst()); + } + assertNull(q.pollFirst()); + } + + /** + * pollLast() succeeds unless empty + */ + public void testPollLast() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + for (int i = SIZE - 1; i >= 0; --i) { + assertEquals(i, q.pollLast()); + } + assertNull(q.pollLast()); + } + + /** + * poll() succeeds unless empty + */ + public void testPoll() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.poll()); + } + assertNull(q.poll()); + } + + /** + * peek() returns next element, or null if empty + */ + public void testPeek() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.peek()); + assertEquals(i, q.poll()); + assertTrue(q.peek() == null || + !q.peek().equals(i)); + } + assertNull(q.peek()); + } + + /** + * element() returns first element, or throws NSEE if empty + */ + public void testElement() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.element()); + assertEquals(i, q.poll()); + } + try { + q.element(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * remove() removes next element, or throws NSEE if empty + */ + public void testRemove() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.remove()); + } + try { + q.remove(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * remove(x) removes x and returns true if present + */ + public void testRemoveElement() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + for (int i = 1; i < SIZE; i += 2) { + assertTrue(q.contains(i)); + assertTrue(q.remove(i)); + assertFalse(q.contains(i)); + assertTrue(q.contains(i - 1)); + } + for (int i = 0; i < SIZE; i += 2) { + assertTrue(q.contains(i)); + assertTrue(q.remove(i)); + assertFalse(q.contains(i)); + assertFalse(q.remove(i + 1)); + assertFalse(q.contains(i + 1)); + } + assertTrue(q.isEmpty()); + } + + /** + * peekFirst() returns next element, or null if empty + */ + public void testPeekFirst() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.peekFirst()); + assertEquals(i, q.pollFirst()); + assertTrue(q.peekFirst() == null || + !q.peekFirst().equals(i)); + } + assertNull(q.peekFirst()); + } + + /** + * peekLast() returns next element, or null if empty + */ + public void testPeekLast() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + for (int i = SIZE - 1; i >= 0; --i) { + assertEquals(i, q.peekLast()); + assertEquals(i, q.pollLast()); + assertTrue(q.peekLast() == null || + !q.peekLast().equals(i)); + } + assertNull(q.peekLast()); + } + + /** + * getFirst() returns first element, or throws NSEE if empty + */ + public void testFirstElement() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.getFirst()); + assertEquals(i, q.pollFirst()); + } + try { + q.getFirst(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * getLast() returns last element, or throws NSEE if empty + */ + public void testLastElement() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + for (int i = SIZE - 1; i >= 0; --i) { + assertEquals(i, q.getLast()); + assertEquals(i, q.pollLast()); + } + try { + q.getLast(); + shouldThrow(); + } catch (NoSuchElementException success) {} + assertNull(q.peekLast()); + } + + /** + * removeFirst() removes first element, or throws NSEE if empty + */ + public void testRemoveFirst() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.removeFirst()); + } + try { + q.removeFirst(); + shouldThrow(); + } catch (NoSuchElementException success) {} + assertNull(q.peekFirst()); + } + + /** + * removeLast() removes last element, or throws NSEE if empty + */ + public void testRemoveLast() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + for (int i = SIZE - 1; i >= 0; --i) { + assertEquals(i, q.removeLast()); + } + try { + q.removeLast(); + shouldThrow(); + } catch (NoSuchElementException success) {} + assertNull(q.peekLast()); + } + + /** + * removeFirstOccurrence(x) removes x and returns true if present + */ + public void testRemoveFirstOccurrence() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + for (int i = 1; i < SIZE; i += 2) { + assertTrue(q.removeFirstOccurrence(new Integer(i))); + } + for (int i = 0; i < SIZE; i += 2) { + assertTrue(q.removeFirstOccurrence(new Integer(i))); + assertFalse(q.removeFirstOccurrence(new Integer(i + 1))); + } + assertTrue(q.isEmpty()); + } + + /** + * removeLastOccurrence(x) removes x and returns true if present + */ + public void testRemoveLastOccurrence() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + for (int i = 1; i < SIZE; i += 2) { + assertTrue(q.removeLastOccurrence(new Integer(i))); + } + for (int i = 0; i < SIZE; i += 2) { + assertTrue(q.removeLastOccurrence(new Integer(i))); + assertFalse(q.removeLastOccurrence(new Integer(i + 1))); + } + assertTrue(q.isEmpty()); + } + + /** + * contains(x) reports true when elements added but not yet removed + */ + public void testContains() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.contains(new Integer(i))); + q.poll(); + assertFalse(q.contains(new Integer(i))); + } + } + + /** + * clear() removes all elements + */ + public void testClear() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + q.clear(); + assertTrue(q.isEmpty()); + assertEquals(0, q.size()); + q.add(one); + assertFalse(q.isEmpty()); + q.clear(); + assertTrue(q.isEmpty()); + } + + /** + * containsAll(c) is true when c contains a subset of elements + */ + public void testContainsAll() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + ConcurrentLinkedDeque p = new ConcurrentLinkedDeque(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.containsAll(p)); + assertFalse(p.containsAll(q)); + p.add(new Integer(i)); + } + assertTrue(p.containsAll(q)); + } + + /** + * retainAll(c) retains only those elements of c and reports true if change + */ + public void testRetainAll() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + ConcurrentLinkedDeque p = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + boolean changed = q.retainAll(p); + if (i == 0) + assertFalse(changed); + else + assertTrue(changed); + + assertTrue(q.containsAll(p)); + assertEquals(SIZE - i, q.size()); + p.remove(); + } + } + + /** + * removeAll(c) removes only those elements of c and reports true if changed + */ + public void testRemoveAll() { + for (int i = 1; i < SIZE; ++i) { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + ConcurrentLinkedDeque p = populatedDeque(i); + assertTrue(q.removeAll(p)); + assertEquals(SIZE - i, q.size()); + for (int j = 0; j < i; ++j) { + Integer x = (Integer)(p.remove()); + assertFalse(q.contains(x)); + } + } + } + + /** + * toArray() contains all elements in FIFO order + */ + public void testToArray() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + Object[] o = q.toArray(); + for (int i = 0; i < o.length; i++) + assertSame(o[i], q.poll()); + } + + /** + * toArray(a) contains all elements in FIFO order + */ + public void testToArray2() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + Integer[] ints = new Integer[SIZE]; + Integer[] array = q.toArray(ints); + assertSame(ints, array); + for (int i = 0; i < ints.length; i++) + assertSame(ints[i], q.poll()); + } + + /** + * toArray(null) throws NullPointerException + */ + public void testToArray_NullArg() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + try { + q.toArray(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * toArray(incompatible array type) throws ArrayStoreException + */ + public void testToArray1_BadArg() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + try { + q.toArray(new String[10]); + shouldThrow(); + } catch (ArrayStoreException success) {} + } + + /** + * Iterator iterates through all elements + */ + public void testIterator() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + Iterator it = q.iterator(); + int i; + for (i = 0; it.hasNext(); i++) + assertTrue(q.contains(it.next())); + assertEquals(i, SIZE); + assertIteratorExhausted(it); + } + + /** + * iterator of empty collection has no elements + */ + public void testEmptyIterator() { + Deque c = new ConcurrentLinkedDeque(); + assertIteratorExhausted(c.iterator()); + assertIteratorExhausted(c.descendingIterator()); + } + + /** + * Iterator ordering is FIFO + */ + public void testIteratorOrdering() { + final ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); + q.add(one); + q.add(two); + q.add(three); + + int k = 0; + for (Iterator it = q.iterator(); it.hasNext();) { + assertEquals(++k, it.next()); + } + + assertEquals(3, k); + } + + /** + * Modifications do not cause iterators to fail + */ + public void testWeaklyConsistentIteration() { + final ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); + q.add(one); + q.add(two); + q.add(three); + + for (Iterator it = q.iterator(); it.hasNext();) { + q.remove(); + it.next(); + } + + assertEquals("deque should be empty again", 0, q.size()); + } + + /** + * iterator.remove() removes current element + */ + public void testIteratorRemove() { + final ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); + final Random rng = new Random(); + for (int iters = 0; iters < 100; ++iters) { + int max = rng.nextInt(5) + 2; + int split = rng.nextInt(max - 1) + 1; + for (int j = 1; j <= max; ++j) + q.add(new Integer(j)); + Iterator it = q.iterator(); + for (int j = 1; j <= split; ++j) + assertEquals(it.next(), new Integer(j)); + it.remove(); + assertEquals(it.next(), new Integer(split + 1)); + for (int j = 1; j <= split; ++j) + q.remove(new Integer(j)); + it = q.iterator(); + for (int j = split + 1; j <= max; ++j) { + assertEquals(it.next(), new Integer(j)); + it.remove(); + } + assertFalse(it.hasNext()); + assertTrue(q.isEmpty()); + } + } + + /** + * Descending iterator iterates through all elements + */ + public void testDescendingIterator() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + int i = 0; + Iterator it = q.descendingIterator(); + while (it.hasNext()) { + assertTrue(q.contains(it.next())); + ++i; + } + assertEquals(i, SIZE); + assertFalse(it.hasNext()); + try { + it.next(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * Descending iterator ordering is reverse FIFO + */ + public void testDescendingIteratorOrdering() { + final ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); + for (int iters = 0; iters < 100; ++iters) { + q.add(new Integer(3)); + q.add(new Integer(2)); + q.add(new Integer(1)); + int k = 0; + for (Iterator it = q.descendingIterator(); it.hasNext();) { + assertEquals(++k, it.next()); + } + + assertEquals(3, k); + q.remove(); + q.remove(); + q.remove(); + } + } + + /** + * descendingIterator.remove() removes current element + */ + public void testDescendingIteratorRemove() { + final ConcurrentLinkedDeque q = new ConcurrentLinkedDeque(); + final Random rng = new Random(); + for (int iters = 0; iters < 100; ++iters) { + int max = rng.nextInt(5) + 2; + int split = rng.nextInt(max - 1) + 1; + for (int j = max; j >= 1; --j) + q.add(new Integer(j)); + Iterator it = q.descendingIterator(); + for (int j = 1; j <= split; ++j) + assertEquals(it.next(), new Integer(j)); + it.remove(); + assertEquals(it.next(), new Integer(split + 1)); + for (int j = 1; j <= split; ++j) + q.remove(new Integer(j)); + it = q.descendingIterator(); + for (int j = split + 1; j <= max; ++j) { + assertEquals(it.next(), new Integer(j)); + it.remove(); + } + assertFalse(it.hasNext()); + assertTrue(q.isEmpty()); + } + } + + /** + * toString() contains toStrings of elements + */ + public void testToString() { + ConcurrentLinkedDeque q = populatedDeque(SIZE); + String s = q.toString(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(s.contains(String.valueOf(i))); + } + } + + /** + * A deserialized serialized deque has same elements in same order + */ + public void testSerialization() throws Exception { + Queue x = populatedDeque(SIZE); + Queue y = serialClone(x); + + assertNotSame(x, y); + assertEquals(x.size(), y.size()); + assertEquals(x.toString(), y.toString()); + assertTrue(Arrays.equals(x.toArray(), y.toArray())); + while (!x.isEmpty()) { + assertFalse(y.isEmpty()); + assertEquals(x.remove(), y.remove()); + } + assertTrue(y.isEmpty()); + } + + /** + * contains(null) always return false. + * remove(null) always throws NullPointerException. + */ + public void testNeverContainsNull() { + Deque[] qs = { + new ConcurrentLinkedDeque(), + populatedDeque(2), + }; + + for (Deque q : qs) { + assertFalse(q.contains(null)); + try { + assertFalse(q.remove(null)); + shouldThrow(); + } catch (NullPointerException success) {} + try { + assertFalse(q.removeFirstOccurrence(null)); + shouldThrow(); + } catch (NullPointerException success) {} + try { + assertFalse(q.removeLastOccurrence(null)); + shouldThrow(); + } catch (NullPointerException success) {} + } + } +} diff --git a/jdk/test/java/util/concurrent/tck/ConcurrentLinkedQueueTest.java b/jdk/test/java/util/concurrent/tck/ConcurrentLinkedQueueTest.java new file mode 100644 index 00000000000..bafaa6581f0 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ConcurrentLinkedQueueTest.java @@ -0,0 +1,564 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ConcurrentLinkedQueueTest extends JSR166TestCase { + + public static void main(String[] args) { + main(suite(), args); + } + + public static Test suite() { + return new TestSuite(ConcurrentLinkedQueueTest.class); + } + + /** + * Returns a new queue of given size containing consecutive + * Integers 0 ... n. + */ + private ConcurrentLinkedQueue populatedQueue(int n) { + ConcurrentLinkedQueue q = new ConcurrentLinkedQueue(); + assertTrue(q.isEmpty()); + for (int i = 0; i < n; ++i) + assertTrue(q.offer(new Integer(i))); + assertFalse(q.isEmpty()); + assertEquals(n, q.size()); + return q; + } + + /** + * new queue is empty + */ + public void testConstructor1() { + assertEquals(0, new ConcurrentLinkedQueue().size()); + } + + /** + * Initializing from null Collection throws NPE + */ + public void testConstructor3() { + try { + new ConcurrentLinkedQueue((Collection)null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing from Collection of null elements throws NPE + */ + public void testConstructor4() { + try { + new ConcurrentLinkedQueue(Arrays.asList(new Integer[SIZE])); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing from Collection with some null elements throws NPE + */ + public void testConstructor5() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); + try { + new ConcurrentLinkedQueue(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Queue contains all elements of collection used to initialize + */ + public void testConstructor6() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(i); + ConcurrentLinkedQueue q = new ConcurrentLinkedQueue(Arrays.asList(ints)); + for (int i = 0; i < SIZE; ++i) + assertEquals(ints[i], q.poll()); + } + + /** + * isEmpty is true before add, false after + */ + public void testEmpty() { + ConcurrentLinkedQueue q = new ConcurrentLinkedQueue(); + assertTrue(q.isEmpty()); + q.add(one); + assertFalse(q.isEmpty()); + q.add(two); + q.remove(); + q.remove(); + assertTrue(q.isEmpty()); + } + + /** + * size changes when elements added and removed + */ + public void testSize() { + ConcurrentLinkedQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(SIZE - i, q.size()); + q.remove(); + } + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.size()); + q.add(new Integer(i)); + } + } + + /** + * offer(null) throws NPE + */ + public void testOfferNull() { + ConcurrentLinkedQueue q = new ConcurrentLinkedQueue(); + try { + q.offer(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * add(null) throws NPE + */ + public void testAddNull() { + ConcurrentLinkedQueue q = new ConcurrentLinkedQueue(); + try { + q.add(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Offer returns true + */ + public void testOffer() { + ConcurrentLinkedQueue q = new ConcurrentLinkedQueue(); + assertTrue(q.offer(zero)); + assertTrue(q.offer(one)); + } + + /** + * add returns true + */ + public void testAdd() { + ConcurrentLinkedQueue q = new ConcurrentLinkedQueue(); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.size()); + assertTrue(q.add(new Integer(i))); + } + } + + /** + * addAll(null) throws NPE + */ + public void testAddAll1() { + ConcurrentLinkedQueue q = new ConcurrentLinkedQueue(); + try { + q.addAll(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll(this) throws IAE + */ + public void testAddAllSelf() { + ConcurrentLinkedQueue q = populatedQueue(SIZE); + try { + q.addAll(q); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * addAll of a collection with null elements throws NPE + */ + public void testAddAll2() { + ConcurrentLinkedQueue q = new ConcurrentLinkedQueue(); + try { + q.addAll(Arrays.asList(new Integer[SIZE])); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll of a collection with any null elements throws NPE after + * possibly adding some elements + */ + public void testAddAll3() { + ConcurrentLinkedQueue q = new ConcurrentLinkedQueue(); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); + try { + q.addAll(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Queue contains all elements, in traversal order, of successful addAll + */ + public void testAddAll5() { + Integer[] empty = new Integer[0]; + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(i); + ConcurrentLinkedQueue q = new ConcurrentLinkedQueue(); + assertFalse(q.addAll(Arrays.asList(empty))); + assertTrue(q.addAll(Arrays.asList(ints))); + for (int i = 0; i < SIZE; ++i) + assertEquals(ints[i], q.poll()); + } + + /** + * poll succeeds unless empty + */ + public void testPoll() { + ConcurrentLinkedQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.poll()); + } + assertNull(q.poll()); + } + + /** + * peek returns next element, or null if empty + */ + public void testPeek() { + ConcurrentLinkedQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.peek()); + assertEquals(i, q.poll()); + assertTrue(q.peek() == null || + !q.peek().equals(i)); + } + assertNull(q.peek()); + } + + /** + * element returns next element, or throws NSEE if empty + */ + public void testElement() { + ConcurrentLinkedQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.element()); + assertEquals(i, q.poll()); + } + try { + q.element(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * remove removes next element, or throws NSEE if empty + */ + public void testRemove() { + ConcurrentLinkedQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.remove()); + } + try { + q.remove(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * remove(x) removes x and returns true if present + */ + public void testRemoveElement() { + ConcurrentLinkedQueue q = populatedQueue(SIZE); + for (int i = 1; i < SIZE; i += 2) { + assertTrue(q.contains(i)); + assertTrue(q.remove(i)); + assertFalse(q.contains(i)); + assertTrue(q.contains(i - 1)); + } + for (int i = 0; i < SIZE; i += 2) { + assertTrue(q.contains(i)); + assertTrue(q.remove(i)); + assertFalse(q.contains(i)); + assertFalse(q.remove(i + 1)); + assertFalse(q.contains(i + 1)); + } + assertTrue(q.isEmpty()); + } + + /** + * contains(x) reports true when elements added but not yet removed + */ + public void testContains() { + ConcurrentLinkedQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.contains(new Integer(i))); + q.poll(); + assertFalse(q.contains(new Integer(i))); + } + } + + /** + * clear removes all elements + */ + public void testClear() { + ConcurrentLinkedQueue q = populatedQueue(SIZE); + q.clear(); + assertTrue(q.isEmpty()); + assertEquals(0, q.size()); + q.add(one); + assertFalse(q.isEmpty()); + q.clear(); + assertTrue(q.isEmpty()); + } + + /** + * containsAll(c) is true when c contains a subset of elements + */ + public void testContainsAll() { + ConcurrentLinkedQueue q = populatedQueue(SIZE); + ConcurrentLinkedQueue p = new ConcurrentLinkedQueue(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.containsAll(p)); + assertFalse(p.containsAll(q)); + p.add(new Integer(i)); + } + assertTrue(p.containsAll(q)); + } + + /** + * retainAll(c) retains only those elements of c and reports true if change + */ + public void testRetainAll() { + ConcurrentLinkedQueue q = populatedQueue(SIZE); + ConcurrentLinkedQueue p = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + boolean changed = q.retainAll(p); + if (i == 0) + assertFalse(changed); + else + assertTrue(changed); + + assertTrue(q.containsAll(p)); + assertEquals(SIZE - i, q.size()); + p.remove(); + } + } + + /** + * removeAll(c) removes only those elements of c and reports true if changed + */ + public void testRemoveAll() { + for (int i = 1; i < SIZE; ++i) { + ConcurrentLinkedQueue q = populatedQueue(SIZE); + ConcurrentLinkedQueue p = populatedQueue(i); + assertTrue(q.removeAll(p)); + assertEquals(SIZE - i, q.size()); + for (int j = 0; j < i; ++j) { + Integer x = (Integer)(p.remove()); + assertFalse(q.contains(x)); + } + } + } + + /** + * toArray contains all elements in FIFO order + */ + public void testToArray() { + ConcurrentLinkedQueue q = populatedQueue(SIZE); + Object[] o = q.toArray(); + for (int i = 0; i < o.length; i++) + assertSame(o[i], q.poll()); + } + + /** + * toArray(a) contains all elements in FIFO order + */ + public void testToArray2() { + ConcurrentLinkedQueue q = populatedQueue(SIZE); + Integer[] ints = new Integer[SIZE]; + Integer[] array = q.toArray(ints); + assertSame(ints, array); + for (int i = 0; i < ints.length; i++) + assertSame(ints[i], q.poll()); + } + + /** + * toArray(null) throws NullPointerException + */ + public void testToArray_NullArg() { + ConcurrentLinkedQueue q = populatedQueue(SIZE); + try { + q.toArray(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * toArray(incompatible array type) throws ArrayStoreException + */ + public void testToArray1_BadArg() { + ConcurrentLinkedQueue q = populatedQueue(SIZE); + try { + q.toArray(new String[10]); + shouldThrow(); + } catch (ArrayStoreException success) {} + } + + /** + * iterator iterates through all elements + */ + public void testIterator() { + ConcurrentLinkedQueue q = populatedQueue(SIZE); + Iterator it = q.iterator(); + int i; + for (i = 0; it.hasNext(); i++) + assertTrue(q.contains(it.next())); + assertEquals(i, SIZE); + assertIteratorExhausted(it); + } + + /** + * iterator of empty collection has no elements + */ + public void testEmptyIterator() { + assertIteratorExhausted(new ConcurrentLinkedQueue().iterator()); + } + + /** + * iterator ordering is FIFO + */ + public void testIteratorOrdering() { + final ConcurrentLinkedQueue q = new ConcurrentLinkedQueue(); + q.add(one); + q.add(two); + q.add(three); + + int k = 0; + for (Iterator it = q.iterator(); it.hasNext();) { + assertEquals(++k, it.next()); + } + + assertEquals(3, k); + } + + /** + * Modifications do not cause iterators to fail + */ + public void testWeaklyConsistentIteration() { + final ConcurrentLinkedQueue q = new ConcurrentLinkedQueue(); + q.add(one); + q.add(two); + q.add(three); + + for (Iterator it = q.iterator(); it.hasNext();) { + q.remove(); + it.next(); + } + + assertEquals("queue should be empty again", 0, q.size()); + } + + /** + * iterator.remove removes current element + */ + public void testIteratorRemove() { + final ConcurrentLinkedQueue q = new ConcurrentLinkedQueue(); + q.add(one); + q.add(two); + q.add(three); + Iterator it = q.iterator(); + it.next(); + it.remove(); + it = q.iterator(); + assertSame(it.next(), two); + assertSame(it.next(), three); + assertFalse(it.hasNext()); + } + + /** + * toString contains toStrings of elements + */ + public void testToString() { + ConcurrentLinkedQueue q = populatedQueue(SIZE); + String s = q.toString(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(s.contains(String.valueOf(i))); + } + } + + /** + * A deserialized serialized queue has same elements in same order + */ + public void testSerialization() throws Exception { + Queue x = populatedQueue(SIZE); + Queue y = serialClone(x); + + assertNotSame(x, y); + assertEquals(x.size(), y.size()); + assertEquals(x.toString(), y.toString()); + assertTrue(Arrays.equals(x.toArray(), y.toArray())); + while (!x.isEmpty()) { + assertFalse(y.isEmpty()); + assertEquals(x.remove(), y.remove()); + } + assertTrue(y.isEmpty()); + } + + /** + * remove(null), contains(null) always return false + */ + public void testNeverContainsNull() { + Collection[] qs = { + new ConcurrentLinkedQueue(), + populatedQueue(2), + }; + + for (Collection q : qs) { + assertFalse(q.contains(null)); + assertFalse(q.remove(null)); + } + } +} diff --git a/jdk/test/java/util/concurrent/tck/ConcurrentSkipListMapTest.java b/jdk/test/java/util/concurrent/tck/ConcurrentSkipListMapTest.java new file mode 100644 index 00000000000..515303e3e64 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ConcurrentSkipListMapTest.java @@ -0,0 +1,1306 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.NavigableMap; +import java.util.NavigableSet; +import java.util.NoSuchElementException; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.ConcurrentSkipListMap; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ConcurrentSkipListMapTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(ConcurrentSkipListMapTest.class); + } + + /** + * Returns a new map from Integers 1-5 to Strings "A"-"E". + */ + private static ConcurrentSkipListMap map5() { + ConcurrentSkipListMap map = new ConcurrentSkipListMap(); + assertTrue(map.isEmpty()); + map.put(one, "A"); + map.put(five, "E"); + map.put(three, "C"); + map.put(two, "B"); + map.put(four, "D"); + assertFalse(map.isEmpty()); + assertEquals(5, map.size()); + return map; + } + + /** + * clear removes all pairs + */ + public void testClear() { + ConcurrentSkipListMap map = map5(); + map.clear(); + assertEquals(0, map.size()); + } + + /** + * copy constructor creates map equal to source map + */ + public void testConstructFromSorted() { + ConcurrentSkipListMap map = map5(); + ConcurrentSkipListMap map2 = new ConcurrentSkipListMap(map); + assertEquals(map, map2); + } + + /** + * Maps with same contents are equal + */ + public void testEquals() { + ConcurrentSkipListMap map1 = map5(); + ConcurrentSkipListMap map2 = map5(); + assertEquals(map1, map2); + assertEquals(map2, map1); + map1.clear(); + assertFalse(map1.equals(map2)); + assertFalse(map2.equals(map1)); + } + + /** + * containsKey returns true for contained key + */ + public void testContainsKey() { + ConcurrentSkipListMap map = map5(); + assertTrue(map.containsKey(one)); + assertFalse(map.containsKey(zero)); + } + + /** + * containsValue returns true for held values + */ + public void testContainsValue() { + ConcurrentSkipListMap map = map5(); + assertTrue(map.containsValue("A")); + assertFalse(map.containsValue("Z")); + } + + /** + * get returns the correct element at the given key, + * or null if not present + */ + public void testGet() { + ConcurrentSkipListMap map = map5(); + assertEquals("A", (String)map.get(one)); + ConcurrentSkipListMap empty = new ConcurrentSkipListMap(); + assertNull(empty.get(one)); + } + + /** + * isEmpty is true of empty map and false for non-empty + */ + public void testIsEmpty() { + ConcurrentSkipListMap empty = new ConcurrentSkipListMap(); + ConcurrentSkipListMap map = map5(); + assertTrue(empty.isEmpty()); + assertFalse(map.isEmpty()); + } + + /** + * firstKey returns first key + */ + public void testFirstKey() { + ConcurrentSkipListMap map = map5(); + assertEquals(one, map.firstKey()); + } + + /** + * lastKey returns last key + */ + public void testLastKey() { + ConcurrentSkipListMap map = map5(); + assertEquals(five, map.lastKey()); + } + + /** + * keySet.toArray returns contains all keys + */ + public void testKeySetToArray() { + ConcurrentSkipListMap map = map5(); + Set s = map.keySet(); + Object[] ar = s.toArray(); + assertTrue(s.containsAll(Arrays.asList(ar))); + assertEquals(5, ar.length); + ar[0] = m10; + assertFalse(s.containsAll(Arrays.asList(ar))); + } + + /** + * descendingkeySet.toArray returns contains all keys + */ + public void testDescendingKeySetToArray() { + ConcurrentSkipListMap map = map5(); + Set s = map.descendingKeySet(); + Object[] ar = s.toArray(); + assertEquals(5, ar.length); + assertTrue(s.containsAll(Arrays.asList(ar))); + ar[0] = m10; + assertFalse(s.containsAll(Arrays.asList(ar))); + } + + /** + * keySet returns a Set containing all the keys + */ + public void testKeySet() { + ConcurrentSkipListMap map = map5(); + Set s = map.keySet(); + assertEquals(5, s.size()); + assertTrue(s.contains(one)); + assertTrue(s.contains(two)); + assertTrue(s.contains(three)); + assertTrue(s.contains(four)); + assertTrue(s.contains(five)); + } + + /** + * keySet is ordered + */ + public void testKeySetOrder() { + ConcurrentSkipListMap map = map5(); + Set s = map.keySet(); + Iterator i = s.iterator(); + Integer last = (Integer)i.next(); + assertEquals(last, one); + int count = 1; + while (i.hasNext()) { + Integer k = (Integer)i.next(); + assertTrue(last.compareTo(k) < 0); + last = k; + ++count; + } + assertEquals(5, count); + } + + /** + * descending iterator of key set is inverse ordered + */ + public void testKeySetDescendingIteratorOrder() { + ConcurrentSkipListMap map = map5(); + NavigableSet s = map.navigableKeySet(); + Iterator i = s.descendingIterator(); + Integer last = (Integer)i.next(); + assertEquals(last, five); + int count = 1; + while (i.hasNext()) { + Integer k = (Integer)i.next(); + assertTrue(last.compareTo(k) > 0); + last = k; + ++count; + } + assertEquals(5, count); + } + + /** + * descendingKeySet is ordered + */ + public void testDescendingKeySetOrder() { + ConcurrentSkipListMap map = map5(); + Set s = map.descendingKeySet(); + Iterator i = s.iterator(); + Integer last = (Integer)i.next(); + assertEquals(last, five); + int count = 1; + while (i.hasNext()) { + Integer k = (Integer)i.next(); + assertTrue(last.compareTo(k) > 0); + last = k; + ++count; + } + assertEquals(5, count); + } + + /** + * descending iterator of descendingKeySet is ordered + */ + public void testDescendingKeySetDescendingIteratorOrder() { + ConcurrentSkipListMap map = map5(); + NavigableSet s = map.descendingKeySet(); + Iterator i = s.descendingIterator(); + Integer last = (Integer)i.next(); + assertEquals(last, one); + int count = 1; + while (i.hasNext()) { + Integer k = (Integer)i.next(); + assertTrue(last.compareTo(k) < 0); + last = k; + ++count; + } + assertEquals(5, count); + } + + /** + * Values.toArray contains all values + */ + public void testValuesToArray() { + ConcurrentSkipListMap map = map5(); + Collection v = map.values(); + Object[] ar = v.toArray(); + ArrayList s = new ArrayList(Arrays.asList(ar)); + assertEquals(5, ar.length); + assertTrue(s.contains("A")); + assertTrue(s.contains("B")); + assertTrue(s.contains("C")); + assertTrue(s.contains("D")); + assertTrue(s.contains("E")); + } + + /** + * values collection contains all values + */ + public void testValues() { + ConcurrentSkipListMap map = map5(); + Collection s = map.values(); + assertEquals(5, s.size()); + assertTrue(s.contains("A")); + assertTrue(s.contains("B")); + assertTrue(s.contains("C")); + assertTrue(s.contains("D")); + assertTrue(s.contains("E")); + } + + /** + * entrySet contains all pairs + */ + public void testEntrySet() { + ConcurrentSkipListMap map = map5(); + Set s = map.entrySet(); + assertEquals(5, s.size()); + Iterator it = s.iterator(); + while (it.hasNext()) { + Map.Entry e = (Map.Entry) it.next(); + assertTrue( + (e.getKey().equals(one) && e.getValue().equals("A")) || + (e.getKey().equals(two) && e.getValue().equals("B")) || + (e.getKey().equals(three) && e.getValue().equals("C")) || + (e.getKey().equals(four) && e.getValue().equals("D")) || + (e.getKey().equals(five) && e.getValue().equals("E"))); + } + } + + /** + * descendingEntrySet contains all pairs + */ + public void testDescendingEntrySet() { + ConcurrentSkipListMap map = map5(); + Set s = map.descendingMap().entrySet(); + assertEquals(5, s.size()); + Iterator it = s.iterator(); + while (it.hasNext()) { + Map.Entry e = (Map.Entry) it.next(); + assertTrue( + (e.getKey().equals(one) && e.getValue().equals("A")) || + (e.getKey().equals(two) && e.getValue().equals("B")) || + (e.getKey().equals(three) && e.getValue().equals("C")) || + (e.getKey().equals(four) && e.getValue().equals("D")) || + (e.getKey().equals(five) && e.getValue().equals("E"))); + } + } + + /** + * entrySet.toArray contains all entries + */ + public void testEntrySetToArray() { + ConcurrentSkipListMap map = map5(); + Set s = map.entrySet(); + Object[] ar = s.toArray(); + assertEquals(5, ar.length); + for (int i = 0; i < 5; ++i) { + assertTrue(map.containsKey(((Map.Entry)(ar[i])).getKey())); + assertTrue(map.containsValue(((Map.Entry)(ar[i])).getValue())); + } + } + + /** + * descendingEntrySet.toArray contains all entries + */ + public void testDescendingEntrySetToArray() { + ConcurrentSkipListMap map = map5(); + Set s = map.descendingMap().entrySet(); + Object[] ar = s.toArray(); + assertEquals(5, ar.length); + for (int i = 0; i < 5; ++i) { + assertTrue(map.containsKey(((Map.Entry)(ar[i])).getKey())); + assertTrue(map.containsValue(((Map.Entry)(ar[i])).getValue())); + } + } + + /** + * putAll adds all key-value pairs from the given map + */ + public void testPutAll() { + ConcurrentSkipListMap empty = new ConcurrentSkipListMap(); + ConcurrentSkipListMap map = map5(); + empty.putAll(map); + assertEquals(5, empty.size()); + assertTrue(empty.containsKey(one)); + assertTrue(empty.containsKey(two)); + assertTrue(empty.containsKey(three)); + assertTrue(empty.containsKey(four)); + assertTrue(empty.containsKey(five)); + } + + /** + * putIfAbsent works when the given key is not present + */ + public void testPutIfAbsent() { + ConcurrentSkipListMap map = map5(); + map.putIfAbsent(six, "Z"); + assertTrue(map.containsKey(six)); + } + + /** + * putIfAbsent does not add the pair if the key is already present + */ + public void testPutIfAbsent2() { + ConcurrentSkipListMap map = map5(); + assertEquals("A", map.putIfAbsent(one, "Z")); + } + + /** + * replace fails when the given key is not present + */ + public void testReplace() { + ConcurrentSkipListMap map = map5(); + assertNull(map.replace(six, "Z")); + assertFalse(map.containsKey(six)); + } + + /** + * replace succeeds if the key is already present + */ + public void testReplace2() { + ConcurrentSkipListMap map = map5(); + assertNotNull(map.replace(one, "Z")); + assertEquals("Z", map.get(one)); + } + + /** + * replace value fails when the given key not mapped to expected value + */ + public void testReplaceValue() { + ConcurrentSkipListMap map = map5(); + assertEquals("A", map.get(one)); + assertFalse(map.replace(one, "Z", "Z")); + assertEquals("A", map.get(one)); + } + + /** + * replace value succeeds when the given key mapped to expected value + */ + public void testReplaceValue2() { + ConcurrentSkipListMap map = map5(); + assertEquals("A", map.get(one)); + assertTrue(map.replace(one, "A", "Z")); + assertEquals("Z", map.get(one)); + } + + /** + * remove removes the correct key-value pair from the map + */ + public void testRemove() { + ConcurrentSkipListMap map = map5(); + map.remove(five); + assertEquals(4, map.size()); + assertFalse(map.containsKey(five)); + } + + /** + * remove(key,value) removes only if pair present + */ + public void testRemove2() { + ConcurrentSkipListMap map = map5(); + assertTrue(map.containsKey(five)); + assertEquals("E", map.get(five)); + map.remove(five, "E"); + assertEquals(4, map.size()); + assertFalse(map.containsKey(five)); + map.remove(four, "A"); + assertEquals(4, map.size()); + assertTrue(map.containsKey(four)); + } + + /** + * lowerEntry returns preceding entry. + */ + public void testLowerEntry() { + ConcurrentSkipListMap map = map5(); + Map.Entry e1 = map.lowerEntry(three); + assertEquals(two, e1.getKey()); + + Map.Entry e2 = map.lowerEntry(six); + assertEquals(five, e2.getKey()); + + Map.Entry e3 = map.lowerEntry(one); + assertNull(e3); + + Map.Entry e4 = map.lowerEntry(zero); + assertNull(e4); + } + + /** + * higherEntry returns next entry. + */ + public void testHigherEntry() { + ConcurrentSkipListMap map = map5(); + Map.Entry e1 = map.higherEntry(three); + assertEquals(four, e1.getKey()); + + Map.Entry e2 = map.higherEntry(zero); + assertEquals(one, e2.getKey()); + + Map.Entry e3 = map.higherEntry(five); + assertNull(e3); + + Map.Entry e4 = map.higherEntry(six); + assertNull(e4); + } + + /** + * floorEntry returns preceding entry. + */ + public void testFloorEntry() { + ConcurrentSkipListMap map = map5(); + Map.Entry e1 = map.floorEntry(three); + assertEquals(three, e1.getKey()); + + Map.Entry e2 = map.floorEntry(six); + assertEquals(five, e2.getKey()); + + Map.Entry e3 = map.floorEntry(one); + assertEquals(one, e3.getKey()); + + Map.Entry e4 = map.floorEntry(zero); + assertNull(e4); + } + + /** + * ceilingEntry returns next entry. + */ + public void testCeilingEntry() { + ConcurrentSkipListMap map = map5(); + Map.Entry e1 = map.ceilingEntry(three); + assertEquals(three, e1.getKey()); + + Map.Entry e2 = map.ceilingEntry(zero); + assertEquals(one, e2.getKey()); + + Map.Entry e3 = map.ceilingEntry(five); + assertEquals(five, e3.getKey()); + + Map.Entry e4 = map.ceilingEntry(six); + assertNull(e4); + } + + /** + * lowerEntry, higherEntry, ceilingEntry, and floorEntry return + * immutable entries + */ + public void testEntryImmutability() { + ConcurrentSkipListMap map = map5(); + Map.Entry e = map.lowerEntry(three); + assertEquals(two, e.getKey()); + try { + e.setValue("X"); + shouldThrow(); + } catch (UnsupportedOperationException success) {} + e = map.higherEntry(zero); + assertEquals(one, e.getKey()); + try { + e.setValue("X"); + shouldThrow(); + } catch (UnsupportedOperationException success) {} + e = map.floorEntry(one); + assertEquals(one, e.getKey()); + try { + e.setValue("X"); + shouldThrow(); + } catch (UnsupportedOperationException success) {} + e = map.ceilingEntry(five); + assertEquals(five, e.getKey()); + try { + e.setValue("X"); + shouldThrow(); + } catch (UnsupportedOperationException success) {} + } + + /** + * lowerKey returns preceding element + */ + public void testLowerKey() { + ConcurrentSkipListMap q = map5(); + Object e1 = q.lowerKey(three); + assertEquals(two, e1); + + Object e2 = q.lowerKey(six); + assertEquals(five, e2); + + Object e3 = q.lowerKey(one); + assertNull(e3); + + Object e4 = q.lowerKey(zero); + assertNull(e4); + } + + /** + * higherKey returns next element + */ + public void testHigherKey() { + ConcurrentSkipListMap q = map5(); + Object e1 = q.higherKey(three); + assertEquals(four, e1); + + Object e2 = q.higherKey(zero); + assertEquals(one, e2); + + Object e3 = q.higherKey(five); + assertNull(e3); + + Object e4 = q.higherKey(six); + assertNull(e4); + } + + /** + * floorKey returns preceding element + */ + public void testFloorKey() { + ConcurrentSkipListMap q = map5(); + Object e1 = q.floorKey(three); + assertEquals(three, e1); + + Object e2 = q.floorKey(six); + assertEquals(five, e2); + + Object e3 = q.floorKey(one); + assertEquals(one, e3); + + Object e4 = q.floorKey(zero); + assertNull(e4); + } + + /** + * ceilingKey returns next element + */ + public void testCeilingKey() { + ConcurrentSkipListMap q = map5(); + Object e1 = q.ceilingKey(three); + assertEquals(three, e1); + + Object e2 = q.ceilingKey(zero); + assertEquals(one, e2); + + Object e3 = q.ceilingKey(five); + assertEquals(five, e3); + + Object e4 = q.ceilingKey(six); + assertNull(e4); + } + + /** + * pollFirstEntry returns entries in order + */ + public void testPollFirstEntry() { + ConcurrentSkipListMap map = map5(); + Map.Entry e = map.pollFirstEntry(); + assertEquals(one, e.getKey()); + assertEquals("A", e.getValue()); + e = map.pollFirstEntry(); + assertEquals(two, e.getKey()); + map.put(one, "A"); + e = map.pollFirstEntry(); + assertEquals(one, e.getKey()); + assertEquals("A", e.getValue()); + e = map.pollFirstEntry(); + assertEquals(three, e.getKey()); + map.remove(four); + e = map.pollFirstEntry(); + assertEquals(five, e.getKey()); + try { + e.setValue("A"); + shouldThrow(); + } catch (UnsupportedOperationException success) {} + e = map.pollFirstEntry(); + assertNull(e); + } + + /** + * pollLastEntry returns entries in order + */ + public void testPollLastEntry() { + ConcurrentSkipListMap map = map5(); + Map.Entry e = map.pollLastEntry(); + assertEquals(five, e.getKey()); + assertEquals("E", e.getValue()); + e = map.pollLastEntry(); + assertEquals(four, e.getKey()); + map.put(five, "E"); + e = map.pollLastEntry(); + assertEquals(five, e.getKey()); + assertEquals("E", e.getValue()); + e = map.pollLastEntry(); + assertEquals(three, e.getKey()); + map.remove(two); + e = map.pollLastEntry(); + assertEquals(one, e.getKey()); + try { + e.setValue("E"); + shouldThrow(); + } catch (UnsupportedOperationException success) {} + e = map.pollLastEntry(); + assertNull(e); + } + + /** + * size returns the correct values + */ + public void testSize() { + ConcurrentSkipListMap map = map5(); + ConcurrentSkipListMap empty = new ConcurrentSkipListMap(); + assertEquals(0, empty.size()); + assertEquals(5, map.size()); + } + + /** + * toString contains toString of elements + */ + public void testToString() { + ConcurrentSkipListMap map = map5(); + String s = map.toString(); + for (int i = 1; i <= 5; ++i) { + assertTrue(s.contains(String.valueOf(i))); + } + } + + // Exception tests + + /** + * get(null) of nonempty map throws NPE + */ + public void testGet_NullPointerException() { + ConcurrentSkipListMap c = map5(); + try { + c.get(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * containsKey(null) of nonempty map throws NPE + */ + public void testContainsKey_NullPointerException() { + ConcurrentSkipListMap c = map5(); + try { + c.containsKey(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * containsValue(null) throws NPE + */ + public void testContainsValue_NullPointerException() { + ConcurrentSkipListMap c = new ConcurrentSkipListMap(); + try { + c.containsValue(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * put(null,x) throws NPE + */ + public void testPut1_NullPointerException() { + ConcurrentSkipListMap c = map5(); + try { + c.put(null, "whatever"); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * putIfAbsent(null, x) throws NPE + */ + public void testPutIfAbsent1_NullPointerException() { + ConcurrentSkipListMap c = map5(); + try { + c.putIfAbsent(null, "whatever"); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * replace(null, x) throws NPE + */ + public void testReplace_NullPointerException() { + ConcurrentSkipListMap c = map5(); + try { + c.replace(null, "whatever"); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * replace(null, x, y) throws NPE + */ + public void testReplaceValue_NullPointerException() { + ConcurrentSkipListMap c = map5(); + try { + c.replace(null, one, "whatever"); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * remove(null) throws NPE + */ + public void testRemove1_NullPointerException() { + ConcurrentSkipListMap c = new ConcurrentSkipListMap(); + c.put("sadsdf", "asdads"); + try { + c.remove(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * remove(null, x) throws NPE + */ + public void testRemove2_NullPointerException() { + ConcurrentSkipListMap c = new ConcurrentSkipListMap(); + c.put("sadsdf", "asdads"); + try { + c.remove(null, "whatever"); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * remove(x, null) returns false + */ + public void testRemove3() { + ConcurrentSkipListMap c = new ConcurrentSkipListMap(); + c.put("sadsdf", "asdads"); + assertFalse(c.remove("sadsdf", null)); + } + + /** + * A deserialized map equals original + */ + public void testSerialization() throws Exception { + NavigableMap x = map5(); + NavigableMap y = serialClone(x); + + assertNotSame(x, y); + assertEquals(x.size(), y.size()); + assertEquals(x.toString(), y.toString()); + assertEquals(x, y); + assertEquals(y, x); + } + + /** + * subMap returns map with keys in requested range + */ + public void testSubMapContents() { + ConcurrentSkipListMap map = map5(); + NavigableMap sm = map.subMap(two, true, four, false); + assertEquals(two, sm.firstKey()); + assertEquals(three, sm.lastKey()); + assertEquals(2, sm.size()); + assertFalse(sm.containsKey(one)); + assertTrue(sm.containsKey(two)); + assertTrue(sm.containsKey(three)); + assertFalse(sm.containsKey(four)); + assertFalse(sm.containsKey(five)); + Iterator i = sm.keySet().iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(two, k); + k = (Integer)(i.next()); + assertEquals(three, k); + assertFalse(i.hasNext()); + Iterator r = sm.descendingKeySet().iterator(); + k = (Integer)(r.next()); + assertEquals(three, k); + k = (Integer)(r.next()); + assertEquals(two, k); + assertFalse(r.hasNext()); + + Iterator j = sm.keySet().iterator(); + j.next(); + j.remove(); + assertFalse(map.containsKey(two)); + assertEquals(4, map.size()); + assertEquals(1, sm.size()); + assertEquals(three, sm.firstKey()); + assertEquals(three, sm.lastKey()); + assertEquals("C", sm.remove(three)); + assertTrue(sm.isEmpty()); + assertEquals(3, map.size()); + } + + public void testSubMapContents2() { + ConcurrentSkipListMap map = map5(); + NavigableMap sm = map.subMap(two, true, three, false); + assertEquals(1, sm.size()); + assertEquals(two, sm.firstKey()); + assertEquals(two, sm.lastKey()); + assertFalse(sm.containsKey(one)); + assertTrue(sm.containsKey(two)); + assertFalse(sm.containsKey(three)); + assertFalse(sm.containsKey(four)); + assertFalse(sm.containsKey(five)); + Iterator i = sm.keySet().iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(two, k); + assertFalse(i.hasNext()); + Iterator r = sm.descendingKeySet().iterator(); + k = (Integer)(r.next()); + assertEquals(two, k); + assertFalse(r.hasNext()); + + Iterator j = sm.keySet().iterator(); + j.next(); + j.remove(); + assertFalse(map.containsKey(two)); + assertEquals(4, map.size()); + assertEquals(0, sm.size()); + assertTrue(sm.isEmpty()); + assertSame(sm.remove(three), null); + assertEquals(4, map.size()); + } + + /** + * headMap returns map with keys in requested range + */ + public void testHeadMapContents() { + ConcurrentSkipListMap map = map5(); + NavigableMap sm = map.headMap(four, false); + assertTrue(sm.containsKey(one)); + assertTrue(sm.containsKey(two)); + assertTrue(sm.containsKey(three)); + assertFalse(sm.containsKey(four)); + assertFalse(sm.containsKey(five)); + Iterator i = sm.keySet().iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(one, k); + k = (Integer)(i.next()); + assertEquals(two, k); + k = (Integer)(i.next()); + assertEquals(three, k); + assertFalse(i.hasNext()); + sm.clear(); + assertTrue(sm.isEmpty()); + assertEquals(2, map.size()); + assertEquals(four, map.firstKey()); + } + + /** + * tailMap returns map with keys in requested range + */ + public void testTailMapContents() { + ConcurrentSkipListMap map = map5(); + NavigableMap sm = map.tailMap(two, true); + assertFalse(sm.containsKey(one)); + assertTrue(sm.containsKey(two)); + assertTrue(sm.containsKey(three)); + assertTrue(sm.containsKey(four)); + assertTrue(sm.containsKey(five)); + Iterator i = sm.keySet().iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(two, k); + k = (Integer)(i.next()); + assertEquals(three, k); + k = (Integer)(i.next()); + assertEquals(four, k); + k = (Integer)(i.next()); + assertEquals(five, k); + assertFalse(i.hasNext()); + Iterator r = sm.descendingKeySet().iterator(); + k = (Integer)(r.next()); + assertEquals(five, k); + k = (Integer)(r.next()); + assertEquals(four, k); + k = (Integer)(r.next()); + assertEquals(three, k); + k = (Integer)(r.next()); + assertEquals(two, k); + assertFalse(r.hasNext()); + + Iterator ei = sm.entrySet().iterator(); + Map.Entry e; + e = (Map.Entry)(ei.next()); + assertEquals(two, e.getKey()); + assertEquals("B", e.getValue()); + e = (Map.Entry)(ei.next()); + assertEquals(three, e.getKey()); + assertEquals("C", e.getValue()); + e = (Map.Entry)(ei.next()); + assertEquals(four, e.getKey()); + assertEquals("D", e.getValue()); + e = (Map.Entry)(ei.next()); + assertEquals(five, e.getKey()); + assertEquals("E", e.getValue()); + assertFalse(i.hasNext()); + + NavigableMap ssm = sm.tailMap(four, true); + assertEquals(four, ssm.firstKey()); + assertEquals(five, ssm.lastKey()); + assertEquals("D", ssm.remove(four)); + assertEquals(1, ssm.size()); + assertEquals(3, sm.size()); + assertEquals(4, map.size()); + } + + Random rnd = new Random(666); + BitSet bs; + + /** + * Submaps of submaps subdivide correctly + */ + public void testRecursiveSubMaps() throws Exception { + int mapSize = expensiveTests ? 1000 : 100; + Class cl = ConcurrentSkipListMap.class; + NavigableMap map = newMap(cl); + bs = new BitSet(mapSize); + + populate(map, mapSize); + check(map, 0, mapSize - 1, true); + check(map.descendingMap(), 0, mapSize - 1, false); + + mutateMap(map, 0, mapSize - 1); + check(map, 0, mapSize - 1, true); + check(map.descendingMap(), 0, mapSize - 1, false); + + bashSubMap(map.subMap(0, true, mapSize, false), + 0, mapSize - 1, true); + } + + static NavigableMap newMap(Class cl) throws Exception { + NavigableMap result = + (NavigableMap) cl.newInstance(); + assertEquals(0, result.size()); + assertFalse(result.keySet().iterator().hasNext()); + return result; + } + + void populate(NavigableMap map, int limit) { + for (int i = 0, n = 2 * limit / 3; i < n; i++) { + int key = rnd.nextInt(limit); + put(map, key); + } + } + + void mutateMap(NavigableMap map, int min, int max) { + int size = map.size(); + int rangeSize = max - min + 1; + + // Remove a bunch of entries directly + for (int i = 0, n = rangeSize / 2; i < n; i++) { + remove(map, min - 5 + rnd.nextInt(rangeSize + 10)); + } + + // Remove a bunch of entries with iterator + for (Iterator it = map.keySet().iterator(); it.hasNext(); ) { + if (rnd.nextBoolean()) { + bs.clear(it.next()); + it.remove(); + } + } + + // Add entries till we're back to original size + while (map.size() < size) { + int key = min + rnd.nextInt(rangeSize); + assertTrue(key >= min && key <= max); + put(map, key); + } + } + + void mutateSubMap(NavigableMap map, int min, int max) { + int size = map.size(); + int rangeSize = max - min + 1; + + // Remove a bunch of entries directly + for (int i = 0, n = rangeSize / 2; i < n; i++) { + remove(map, min - 5 + rnd.nextInt(rangeSize + 10)); + } + + // Remove a bunch of entries with iterator + for (Iterator it = map.keySet().iterator(); it.hasNext(); ) { + if (rnd.nextBoolean()) { + bs.clear(it.next()); + it.remove(); + } + } + + // Add entries till we're back to original size + while (map.size() < size) { + int key = min - 5 + rnd.nextInt(rangeSize + 10); + if (key >= min && key <= max) { + put(map, key); + } else { + try { + map.put(key, 2 * key); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + } + + void put(NavigableMap map, int key) { + if (map.put(key, 2 * key) == null) + bs.set(key); + } + + void remove(NavigableMap map, int key) { + if (map.remove(key) != null) + bs.clear(key); + } + + void bashSubMap(NavigableMap map, + int min, int max, boolean ascending) { + check(map, min, max, ascending); + check(map.descendingMap(), min, max, !ascending); + + mutateSubMap(map, min, max); + check(map, min, max, ascending); + check(map.descendingMap(), min, max, !ascending); + + // Recurse + if (max - min < 2) + return; + int midPoint = (min + max) / 2; + + // headMap - pick direction and endpoint inclusion randomly + boolean incl = rnd.nextBoolean(); + NavigableMap hm = map.headMap(midPoint, incl); + if (ascending) { + if (rnd.nextBoolean()) + bashSubMap(hm, min, midPoint - (incl ? 0 : 1), true); + else + bashSubMap(hm.descendingMap(), min, midPoint - (incl ? 0 : 1), + false); + } else { + if (rnd.nextBoolean()) + bashSubMap(hm, midPoint + (incl ? 0 : 1), max, false); + else + bashSubMap(hm.descendingMap(), midPoint + (incl ? 0 : 1), max, + true); + } + + // tailMap - pick direction and endpoint inclusion randomly + incl = rnd.nextBoolean(); + NavigableMap tm = map.tailMap(midPoint,incl); + if (ascending) { + if (rnd.nextBoolean()) + bashSubMap(tm, midPoint + (incl ? 0 : 1), max, true); + else + bashSubMap(tm.descendingMap(), midPoint + (incl ? 0 : 1), max, + false); + } else { + if (rnd.nextBoolean()) { + bashSubMap(tm, min, midPoint - (incl ? 0 : 1), false); + } else { + bashSubMap(tm.descendingMap(), min, midPoint - (incl ? 0 : 1), + true); + } + } + + // subMap - pick direction and endpoint inclusion randomly + int rangeSize = max - min + 1; + int[] endpoints = new int[2]; + endpoints[0] = min + rnd.nextInt(rangeSize); + endpoints[1] = min + rnd.nextInt(rangeSize); + Arrays.sort(endpoints); + boolean lowIncl = rnd.nextBoolean(); + boolean highIncl = rnd.nextBoolean(); + if (ascending) { + NavigableMap sm = map.subMap( + endpoints[0], lowIncl, endpoints[1], highIncl); + if (rnd.nextBoolean()) + bashSubMap(sm, endpoints[0] + (lowIncl ? 0 : 1), + endpoints[1] - (highIncl ? 0 : 1), true); + else + bashSubMap(sm.descendingMap(), endpoints[0] + (lowIncl ? 0 : 1), + endpoints[1] - (highIncl ? 0 : 1), false); + } else { + NavigableMap sm = map.subMap( + endpoints[1], highIncl, endpoints[0], lowIncl); + if (rnd.nextBoolean()) + bashSubMap(sm, endpoints[0] + (lowIncl ? 0 : 1), + endpoints[1] - (highIncl ? 0 : 1), false); + else + bashSubMap(sm.descendingMap(), endpoints[0] + (lowIncl ? 0 : 1), + endpoints[1] - (highIncl ? 0 : 1), true); + } + } + + /** + * min and max are both inclusive. If max < min, interval is empty. + */ + void check(NavigableMap map, + final int min, final int max, final boolean ascending) { + class ReferenceSet { + int lower(int key) { + return ascending ? lowerAscending(key) : higherAscending(key); + } + int floor(int key) { + return ascending ? floorAscending(key) : ceilingAscending(key); + } + int ceiling(int key) { + return ascending ? ceilingAscending(key) : floorAscending(key); + } + int higher(int key) { + return ascending ? higherAscending(key) : lowerAscending(key); + } + int first() { + return ascending ? firstAscending() : lastAscending(); + } + int last() { + return ascending ? lastAscending() : firstAscending(); + } + int lowerAscending(int key) { + return floorAscending(key - 1); + } + int floorAscending(int key) { + if (key < min) + return -1; + else if (key > max) + key = max; + + // BitSet should support this! Test would run much faster + while (key >= min) { + if (bs.get(key)) + return key; + key--; + } + return -1; + } + int ceilingAscending(int key) { + if (key < min) + key = min; + else if (key > max) + return -1; + int result = bs.nextSetBit(key); + return result > max ? -1 : result; + } + int higherAscending(int key) { + return ceilingAscending(key + 1); + } + private int firstAscending() { + int result = ceilingAscending(min); + return result > max ? -1 : result; + } + private int lastAscending() { + int result = floorAscending(max); + return result < min ? -1 : result; + } + } + ReferenceSet rs = new ReferenceSet(); + + // Test contents using containsKey + int size = 0; + for (int i = min; i <= max; i++) { + boolean bsContainsI = bs.get(i); + assertEquals(bsContainsI, map.containsKey(i)); + if (bsContainsI) + size++; + } + assertEquals(size, map.size()); + + // Test contents using contains keySet iterator + int size2 = 0; + int previousKey = -1; + for (int key : map.keySet()) { + assertTrue(bs.get(key)); + size2++; + assertTrue(previousKey < 0 || + (ascending ? key - previousKey > 0 : key - previousKey < 0)); + previousKey = key; + } + assertEquals(size2, size); + + // Test navigation ops + for (int key = min - 1; key <= max + 1; key++) { + assertEq(map.lowerKey(key), rs.lower(key)); + assertEq(map.floorKey(key), rs.floor(key)); + assertEq(map.higherKey(key), rs.higher(key)); + assertEq(map.ceilingKey(key), rs.ceiling(key)); + } + + // Test extrema + if (map.size() != 0) { + assertEq(map.firstKey(), rs.first()); + assertEq(map.lastKey(), rs.last()); + } else { + assertEq(rs.first(), -1); + assertEq(rs.last(), -1); + try { + map.firstKey(); + shouldThrow(); + } catch (NoSuchElementException success) {} + try { + map.lastKey(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + } + + static void assertEq(Integer i, int j) { + if (i == null) + assertEquals(j, -1); + else + assertEquals((int) i, j); + } + + static boolean eq(Integer i, int j) { + return (i == null) ? j == -1 : i == j; + } + +} diff --git a/jdk/test/java/util/concurrent/tck/ConcurrentSkipListSetTest.java b/jdk/test/java/util/concurrent/tck/ConcurrentSkipListSetTest.java new file mode 100644 index 00000000000..9012417716c --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ConcurrentSkipListSetTest.java @@ -0,0 +1,1007 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NavigableSet; +import java.util.NoSuchElementException; +import java.util.Random; +import java.util.Set; +import java.util.SortedSet; +import java.util.concurrent.ConcurrentSkipListSet; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ConcurrentSkipListSetTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(ConcurrentSkipListSetTest.class); + } + + static class MyReverseComparator implements Comparator { + public int compare(Object x, Object y) { + return ((Comparable)y).compareTo(x); + } + } + + /** + * Returns a new set of given size containing consecutive + * Integers 0 ... n. + */ + private ConcurrentSkipListSet populatedSet(int n) { + ConcurrentSkipListSet q = + new ConcurrentSkipListSet(); + assertTrue(q.isEmpty()); + for (int i = n - 1; i >= 0; i -= 2) + assertTrue(q.add(new Integer(i))); + for (int i = (n & 1); i < n; i += 2) + assertTrue(q.add(new Integer(i))); + assertFalse(q.isEmpty()); + assertEquals(n, q.size()); + return q; + } + + /** + * Returns a new set of first 5 ints. + */ + private ConcurrentSkipListSet set5() { + ConcurrentSkipListSet q = new ConcurrentSkipListSet(); + assertTrue(q.isEmpty()); + q.add(one); + q.add(two); + q.add(three); + q.add(four); + q.add(five); + assertEquals(5, q.size()); + return q; + } + + /** + * A new set has unbounded capacity + */ + public void testConstructor1() { + assertEquals(0, new ConcurrentSkipListSet().size()); + } + + /** + * Initializing from null Collection throws NPE + */ + public void testConstructor3() { + try { + new ConcurrentSkipListSet((Collection)null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing from Collection of null elements throws NPE + */ + public void testConstructor4() { + try { + new ConcurrentSkipListSet(Arrays.asList(new Integer[SIZE])); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing from Collection with some null elements throws NPE + */ + public void testConstructor5() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); + try { + new ConcurrentSkipListSet(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Set contains all elements of collection used to initialize + */ + public void testConstructor6() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(i); + ConcurrentSkipListSet q = new ConcurrentSkipListSet(Arrays.asList(ints)); + for (int i = 0; i < SIZE; ++i) + assertEquals(ints[i], q.pollFirst()); + } + + /** + * The comparator used in constructor is used + */ + public void testConstructor7() { + MyReverseComparator cmp = new MyReverseComparator(); + ConcurrentSkipListSet q = new ConcurrentSkipListSet(cmp); + assertEquals(cmp, q.comparator()); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(i); + q.addAll(Arrays.asList(ints)); + for (int i = SIZE - 1; i >= 0; --i) + assertEquals(ints[i], q.pollFirst()); + } + + /** + * isEmpty is true before add, false after + */ + public void testEmpty() { + ConcurrentSkipListSet q = new ConcurrentSkipListSet(); + assertTrue(q.isEmpty()); + q.add(new Integer(1)); + assertFalse(q.isEmpty()); + q.add(new Integer(2)); + q.pollFirst(); + q.pollFirst(); + assertTrue(q.isEmpty()); + } + + /** + * size changes when elements added and removed + */ + public void testSize() { + ConcurrentSkipListSet q = populatedSet(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(SIZE - i, q.size()); + q.pollFirst(); + } + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.size()); + q.add(new Integer(i)); + } + } + + /** + * add(null) throws NPE + */ + public void testAddNull() { + ConcurrentSkipListSet q = new ConcurrentSkipListSet(); + try { + q.add(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Add of comparable element succeeds + */ + public void testAdd() { + ConcurrentSkipListSet q = new ConcurrentSkipListSet(); + assertTrue(q.add(zero)); + assertTrue(q.add(one)); + } + + /** + * Add of duplicate element fails + */ + public void testAddDup() { + ConcurrentSkipListSet q = new ConcurrentSkipListSet(); + assertTrue(q.add(zero)); + assertFalse(q.add(zero)); + } + + /** + * Add of non-Comparable throws CCE + */ + public void testAddNonComparable() { + ConcurrentSkipListSet q = new ConcurrentSkipListSet(); + try { + q.add(new Object()); + q.add(new Object()); + shouldThrow(); + } catch (ClassCastException success) {} + } + + /** + * addAll(null) throws NPE + */ + public void testAddAll1() { + ConcurrentSkipListSet q = new ConcurrentSkipListSet(); + try { + q.addAll(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll of a collection with null elements throws NPE + */ + public void testAddAll2() { + ConcurrentSkipListSet q = new ConcurrentSkipListSet(); + Integer[] ints = new Integer[SIZE]; + try { + q.addAll(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll of a collection with any null elements throws NPE after + * possibly adding some elements + */ + public void testAddAll3() { + ConcurrentSkipListSet q = new ConcurrentSkipListSet(); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); + try { + q.addAll(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Set contains all elements of successful addAll + */ + public void testAddAll5() { + Integer[] empty = new Integer[0]; + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(SIZE - 1 - i); + ConcurrentSkipListSet q = new ConcurrentSkipListSet(); + assertFalse(q.addAll(Arrays.asList(empty))); + assertTrue(q.addAll(Arrays.asList(ints))); + for (int i = 0; i < SIZE; ++i) + assertEquals(i, q.pollFirst()); + } + + /** + * pollFirst succeeds unless empty + */ + public void testPollFirst() { + ConcurrentSkipListSet q = populatedSet(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.pollFirst()); + } + assertNull(q.pollFirst()); + } + + /** + * pollLast succeeds unless empty + */ + public void testPollLast() { + ConcurrentSkipListSet q = populatedSet(SIZE); + for (int i = SIZE - 1; i >= 0; --i) { + assertEquals(i, q.pollLast()); + } + assertNull(q.pollFirst()); + } + + /** + * remove(x) removes x and returns true if present + */ + public void testRemoveElement() { + ConcurrentSkipListSet q = populatedSet(SIZE); + for (int i = 1; i < SIZE; i += 2) { + assertTrue(q.contains(i)); + assertTrue(q.remove(i)); + assertFalse(q.contains(i)); + assertTrue(q.contains(i - 1)); + } + for (int i = 0; i < SIZE; i += 2) { + assertTrue(q.contains(i)); + assertTrue(q.remove(i)); + assertFalse(q.contains(i)); + assertFalse(q.remove(i + 1)); + assertFalse(q.contains(i + 1)); + } + assertTrue(q.isEmpty()); + } + + /** + * contains(x) reports true when elements added but not yet removed + */ + public void testContains() { + ConcurrentSkipListSet q = populatedSet(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.contains(new Integer(i))); + q.pollFirst(); + assertFalse(q.contains(new Integer(i))); + } + } + + /** + * clear removes all elements + */ + public void testClear() { + ConcurrentSkipListSet q = populatedSet(SIZE); + q.clear(); + assertTrue(q.isEmpty()); + assertEquals(0, q.size()); + q.add(new Integer(1)); + assertFalse(q.isEmpty()); + q.clear(); + assertTrue(q.isEmpty()); + } + + /** + * containsAll(c) is true when c contains a subset of elements + */ + public void testContainsAll() { + ConcurrentSkipListSet q = populatedSet(SIZE); + ConcurrentSkipListSet p = new ConcurrentSkipListSet(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.containsAll(p)); + assertFalse(p.containsAll(q)); + p.add(new Integer(i)); + } + assertTrue(p.containsAll(q)); + } + + /** + * retainAll(c) retains only those elements of c and reports true if changed + */ + public void testRetainAll() { + ConcurrentSkipListSet q = populatedSet(SIZE); + ConcurrentSkipListSet p = populatedSet(SIZE); + for (int i = 0; i < SIZE; ++i) { + boolean changed = q.retainAll(p); + if (i == 0) + assertFalse(changed); + else + assertTrue(changed); + + assertTrue(q.containsAll(p)); + assertEquals(SIZE - i, q.size()); + p.pollFirst(); + } + } + + /** + * removeAll(c) removes only those elements of c and reports true if changed + */ + public void testRemoveAll() { + for (int i = 1; i < SIZE; ++i) { + ConcurrentSkipListSet q = populatedSet(SIZE); + ConcurrentSkipListSet p = populatedSet(i); + assertTrue(q.removeAll(p)); + assertEquals(SIZE - i, q.size()); + for (int j = 0; j < i; ++j) { + Integer x = (Integer)(p.pollFirst()); + assertFalse(q.contains(x)); + } + } + } + + /** + * lower returns preceding element + */ + public void testLower() { + ConcurrentSkipListSet q = set5(); + Object e1 = q.lower(three); + assertEquals(two, e1); + + Object e2 = q.lower(six); + assertEquals(five, e2); + + Object e3 = q.lower(one); + assertNull(e3); + + Object e4 = q.lower(zero); + assertNull(e4); + } + + /** + * higher returns next element + */ + public void testHigher() { + ConcurrentSkipListSet q = set5(); + Object e1 = q.higher(three); + assertEquals(four, e1); + + Object e2 = q.higher(zero); + assertEquals(one, e2); + + Object e3 = q.higher(five); + assertNull(e3); + + Object e4 = q.higher(six); + assertNull(e4); + } + + /** + * floor returns preceding element + */ + public void testFloor() { + ConcurrentSkipListSet q = set5(); + Object e1 = q.floor(three); + assertEquals(three, e1); + + Object e2 = q.floor(six); + assertEquals(five, e2); + + Object e3 = q.floor(one); + assertEquals(one, e3); + + Object e4 = q.floor(zero); + assertNull(e4); + } + + /** + * ceiling returns next element + */ + public void testCeiling() { + ConcurrentSkipListSet q = set5(); + Object e1 = q.ceiling(three); + assertEquals(three, e1); + + Object e2 = q.ceiling(zero); + assertEquals(one, e2); + + Object e3 = q.ceiling(five); + assertEquals(five, e3); + + Object e4 = q.ceiling(six); + assertNull(e4); + } + + /** + * toArray contains all elements in sorted order + */ + public void testToArray() { + ConcurrentSkipListSet q = populatedSet(SIZE); + Object[] o = q.toArray(); + for (int i = 0; i < o.length; i++) + assertSame(o[i], q.pollFirst()); + } + + /** + * toArray(a) contains all elements in sorted order + */ + public void testToArray2() { + ConcurrentSkipListSet q = populatedSet(SIZE); + Integer[] ints = new Integer[SIZE]; + assertSame(ints, q.toArray(ints)); + for (int i = 0; i < ints.length; i++) + assertSame(ints[i], q.pollFirst()); + } + + /** + * iterator iterates through all elements + */ + public void testIterator() { + ConcurrentSkipListSet q = populatedSet(SIZE); + Iterator it = q.iterator(); + int i; + for (i = 0; it.hasNext(); i++) + assertTrue(q.contains(it.next())); + assertEquals(i, SIZE); + assertIteratorExhausted(it); + } + + /** + * iterator of empty set has no elements + */ + public void testEmptyIterator() { + NavigableSet s = new ConcurrentSkipListSet(); + assertIteratorExhausted(s.iterator()); + assertIteratorExhausted(s.descendingSet().iterator()); + } + + /** + * iterator.remove removes current element + */ + public void testIteratorRemove() { + final ConcurrentSkipListSet q = new ConcurrentSkipListSet(); + q.add(new Integer(2)); + q.add(new Integer(1)); + q.add(new Integer(3)); + + Iterator it = q.iterator(); + it.next(); + it.remove(); + + it = q.iterator(); + assertEquals(it.next(), new Integer(2)); + assertEquals(it.next(), new Integer(3)); + assertFalse(it.hasNext()); + } + + /** + * toString contains toStrings of elements + */ + public void testToString() { + ConcurrentSkipListSet q = populatedSet(SIZE); + String s = q.toString(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(s.contains(String.valueOf(i))); + } + } + + /** + * A deserialized serialized set has same elements + */ + public void testSerialization() throws Exception { + NavigableSet x = populatedSet(SIZE); + NavigableSet y = serialClone(x); + + assertNotSame(x, y); + assertEquals(x.size(), y.size()); + assertEquals(x, y); + assertEquals(y, x); + while (!x.isEmpty()) { + assertFalse(y.isEmpty()); + assertEquals(x.pollFirst(), y.pollFirst()); + } + assertTrue(y.isEmpty()); + } + + /** + * subSet returns set with keys in requested range + */ + public void testSubSetContents() { + ConcurrentSkipListSet set = set5(); + SortedSet sm = set.subSet(two, four); + assertEquals(two, sm.first()); + assertEquals(three, sm.last()); + assertEquals(2, sm.size()); + assertFalse(sm.contains(one)); + assertTrue(sm.contains(two)); + assertTrue(sm.contains(three)); + assertFalse(sm.contains(four)); + assertFalse(sm.contains(five)); + Iterator i = sm.iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(two, k); + k = (Integer)(i.next()); + assertEquals(three, k); + assertFalse(i.hasNext()); + Iterator j = sm.iterator(); + j.next(); + j.remove(); + assertFalse(set.contains(two)); + assertEquals(4, set.size()); + assertEquals(1, sm.size()); + assertEquals(three, sm.first()); + assertEquals(three, sm.last()); + assertTrue(sm.remove(three)); + assertTrue(sm.isEmpty()); + assertEquals(3, set.size()); + } + + public void testSubSetContents2() { + ConcurrentSkipListSet set = set5(); + SortedSet sm = set.subSet(two, three); + assertEquals(1, sm.size()); + assertEquals(two, sm.first()); + assertEquals(two, sm.last()); + assertFalse(sm.contains(one)); + assertTrue(sm.contains(two)); + assertFalse(sm.contains(three)); + assertFalse(sm.contains(four)); + assertFalse(sm.contains(five)); + Iterator i = sm.iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(two, k); + assertFalse(i.hasNext()); + Iterator j = sm.iterator(); + j.next(); + j.remove(); + assertFalse(set.contains(two)); + assertEquals(4, set.size()); + assertEquals(0, sm.size()); + assertTrue(sm.isEmpty()); + assertFalse(sm.remove(three)); + assertEquals(4, set.size()); + } + + /** + * headSet returns set with keys in requested range + */ + public void testHeadSetContents() { + ConcurrentSkipListSet set = set5(); + SortedSet sm = set.headSet(four); + assertTrue(sm.contains(one)); + assertTrue(sm.contains(two)); + assertTrue(sm.contains(three)); + assertFalse(sm.contains(four)); + assertFalse(sm.contains(five)); + Iterator i = sm.iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(one, k); + k = (Integer)(i.next()); + assertEquals(two, k); + k = (Integer)(i.next()); + assertEquals(three, k); + assertFalse(i.hasNext()); + sm.clear(); + assertTrue(sm.isEmpty()); + assertEquals(2, set.size()); + assertEquals(four, set.first()); + } + + /** + * tailSet returns set with keys in requested range + */ + public void testTailSetContents() { + ConcurrentSkipListSet set = set5(); + SortedSet sm = set.tailSet(two); + assertFalse(sm.contains(one)); + assertTrue(sm.contains(two)); + assertTrue(sm.contains(three)); + assertTrue(sm.contains(four)); + assertTrue(sm.contains(five)); + Iterator i = sm.iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(two, k); + k = (Integer)(i.next()); + assertEquals(three, k); + k = (Integer)(i.next()); + assertEquals(four, k); + k = (Integer)(i.next()); + assertEquals(five, k); + assertFalse(i.hasNext()); + + SortedSet ssm = sm.tailSet(four); + assertEquals(four, ssm.first()); + assertEquals(five, ssm.last()); + assertTrue(ssm.remove(four)); + assertEquals(1, ssm.size()); + assertEquals(3, sm.size()); + assertEquals(4, set.size()); + } + + Random rnd = new Random(666); + + /** + * Subsets of subsets subdivide correctly + */ + public void testRecursiveSubSets() throws Exception { + int setSize = expensiveTests ? 1000 : 100; + Class cl = ConcurrentSkipListSet.class; + + NavigableSet set = newSet(cl); + BitSet bs = new BitSet(setSize); + + populate(set, setSize, bs); + check(set, 0, setSize - 1, true, bs); + check(set.descendingSet(), 0, setSize - 1, false, bs); + + mutateSet(set, 0, setSize - 1, bs); + check(set, 0, setSize - 1, true, bs); + check(set.descendingSet(), 0, setSize - 1, false, bs); + + bashSubSet(set.subSet(0, true, setSize, false), + 0, setSize - 1, true, bs); + } + + /** + * addAll is idempotent + */ + public void testAddAll_idempotent() throws Exception { + Set x = populatedSet(SIZE); + Set y = new ConcurrentSkipListSet(x); + y.addAll(x); + assertEquals(x, y); + assertEquals(y, x); + } + + static NavigableSet newSet(Class cl) throws Exception { + NavigableSet result = (NavigableSet) cl.newInstance(); + assertEquals(0, result.size()); + assertFalse(result.iterator().hasNext()); + return result; + } + + void populate(NavigableSet set, int limit, BitSet bs) { + for (int i = 0, n = 2 * limit / 3; i < n; i++) { + int element = rnd.nextInt(limit); + put(set, element, bs); + } + } + + void mutateSet(NavigableSet set, int min, int max, BitSet bs) { + int size = set.size(); + int rangeSize = max - min + 1; + + // Remove a bunch of entries directly + for (int i = 0, n = rangeSize / 2; i < n; i++) { + remove(set, min - 5 + rnd.nextInt(rangeSize + 10), bs); + } + + // Remove a bunch of entries with iterator + for (Iterator it = set.iterator(); it.hasNext(); ) { + if (rnd.nextBoolean()) { + bs.clear(it.next()); + it.remove(); + } + } + + // Add entries till we're back to original size + while (set.size() < size) { + int element = min + rnd.nextInt(rangeSize); + assertTrue(element >= min && element <= max); + put(set, element, bs); + } + } + + void mutateSubSet(NavigableSet set, int min, int max, + BitSet bs) { + int size = set.size(); + int rangeSize = max - min + 1; + + // Remove a bunch of entries directly + for (int i = 0, n = rangeSize / 2; i < n; i++) { + remove(set, min - 5 + rnd.nextInt(rangeSize + 10), bs); + } + + // Remove a bunch of entries with iterator + for (Iterator it = set.iterator(); it.hasNext(); ) { + if (rnd.nextBoolean()) { + bs.clear(it.next()); + it.remove(); + } + } + + // Add entries till we're back to original size + while (set.size() < size) { + int element = min - 5 + rnd.nextInt(rangeSize + 10); + if (element >= min && element <= max) { + put(set, element, bs); + } else { + try { + set.add(element); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + } + + void put(NavigableSet set, int element, BitSet bs) { + if (set.add(element)) + bs.set(element); + } + + void remove(NavigableSet set, int element, BitSet bs) { + if (set.remove(element)) + bs.clear(element); + } + + void bashSubSet(NavigableSet set, + int min, int max, boolean ascending, + BitSet bs) { + check(set, min, max, ascending, bs); + check(set.descendingSet(), min, max, !ascending, bs); + + mutateSubSet(set, min, max, bs); + check(set, min, max, ascending, bs); + check(set.descendingSet(), min, max, !ascending, bs); + + // Recurse + if (max - min < 2) + return; + int midPoint = (min + max) / 2; + + // headSet - pick direction and endpoint inclusion randomly + boolean incl = rnd.nextBoolean(); + NavigableSet hm = set.headSet(midPoint, incl); + if (ascending) { + if (rnd.nextBoolean()) + bashSubSet(hm, min, midPoint - (incl ? 0 : 1), true, bs); + else + bashSubSet(hm.descendingSet(), min, midPoint - (incl ? 0 : 1), + false, bs); + } else { + if (rnd.nextBoolean()) + bashSubSet(hm, midPoint + (incl ? 0 : 1), max, false, bs); + else + bashSubSet(hm.descendingSet(), midPoint + (incl ? 0 : 1), max, + true, bs); + } + + // tailSet - pick direction and endpoint inclusion randomly + incl = rnd.nextBoolean(); + NavigableSet tm = set.tailSet(midPoint,incl); + if (ascending) { + if (rnd.nextBoolean()) + bashSubSet(tm, midPoint + (incl ? 0 : 1), max, true, bs); + else + bashSubSet(tm.descendingSet(), midPoint + (incl ? 0 : 1), max, + false, bs); + } else { + if (rnd.nextBoolean()) { + bashSubSet(tm, min, midPoint - (incl ? 0 : 1), false, bs); + } else { + bashSubSet(tm.descendingSet(), min, midPoint - (incl ? 0 : 1), + true, bs); + } + } + + // subSet - pick direction and endpoint inclusion randomly + int rangeSize = max - min + 1; + int[] endpoints = new int[2]; + endpoints[0] = min + rnd.nextInt(rangeSize); + endpoints[1] = min + rnd.nextInt(rangeSize); + Arrays.sort(endpoints); + boolean lowIncl = rnd.nextBoolean(); + boolean highIncl = rnd.nextBoolean(); + if (ascending) { + NavigableSet sm = set.subSet( + endpoints[0], lowIncl, endpoints[1], highIncl); + if (rnd.nextBoolean()) + bashSubSet(sm, endpoints[0] + (lowIncl ? 0 : 1), + endpoints[1] - (highIncl ? 0 : 1), true, bs); + else + bashSubSet(sm.descendingSet(), endpoints[0] + (lowIncl ? 0 : 1), + endpoints[1] - (highIncl ? 0 : 1), false, bs); + } else { + NavigableSet sm = set.subSet( + endpoints[1], highIncl, endpoints[0], lowIncl); + if (rnd.nextBoolean()) + bashSubSet(sm, endpoints[0] + (lowIncl ? 0 : 1), + endpoints[1] - (highIncl ? 0 : 1), false, bs); + else + bashSubSet(sm.descendingSet(), endpoints[0] + (lowIncl ? 0 : 1), + endpoints[1] - (highIncl ? 0 : 1), true, bs); + } + } + + /** + * min and max are both inclusive. If max < min, interval is empty. + */ + void check(NavigableSet set, + final int min, final int max, final boolean ascending, + final BitSet bs) { + class ReferenceSet { + int lower(int element) { + return ascending ? + lowerAscending(element) : higherAscending(element); + } + int floor(int element) { + return ascending ? + floorAscending(element) : ceilingAscending(element); + } + int ceiling(int element) { + return ascending ? + ceilingAscending(element) : floorAscending(element); + } + int higher(int element) { + return ascending ? + higherAscending(element) : lowerAscending(element); + } + int first() { + return ascending ? firstAscending() : lastAscending(); + } + int last() { + return ascending ? lastAscending() : firstAscending(); + } + int lowerAscending(int element) { + return floorAscending(element - 1); + } + int floorAscending(int element) { + if (element < min) + return -1; + else if (element > max) + element = max; + + // BitSet should support this! Test would run much faster + while (element >= min) { + if (bs.get(element)) + return element; + element--; + } + return -1; + } + int ceilingAscending(int element) { + if (element < min) + element = min; + else if (element > max) + return -1; + int result = bs.nextSetBit(element); + return result > max ? -1 : result; + } + int higherAscending(int element) { + return ceilingAscending(element + 1); + } + private int firstAscending() { + int result = ceilingAscending(min); + return result > max ? -1 : result; + } + private int lastAscending() { + int result = floorAscending(max); + return result < min ? -1 : result; + } + } + ReferenceSet rs = new ReferenceSet(); + + // Test contents using containsElement + int size = 0; + for (int i = min; i <= max; i++) { + boolean bsContainsI = bs.get(i); + assertEquals(bsContainsI, set.contains(i)); + if (bsContainsI) + size++; + } + assertEquals(size, set.size()); + + // Test contents using contains elementSet iterator + int size2 = 0; + int previousElement = -1; + for (int element : set) { + assertTrue(bs.get(element)); + size2++; + assertTrue(previousElement < 0 || (ascending ? + element - previousElement > 0 : element - previousElement < 0)); + previousElement = element; + } + assertEquals(size2, size); + + // Test navigation ops + for (int element = min - 1; element <= max + 1; element++) { + assertEq(set.lower(element), rs.lower(element)); + assertEq(set.floor(element), rs.floor(element)); + assertEq(set.higher(element), rs.higher(element)); + assertEq(set.ceiling(element), rs.ceiling(element)); + } + + // Test extrema + if (set.size() != 0) { + assertEq(set.first(), rs.first()); + assertEq(set.last(), rs.last()); + } else { + assertEq(rs.first(), -1); + assertEq(rs.last(), -1); + try { + set.first(); + shouldThrow(); + } catch (NoSuchElementException success) {} + try { + set.last(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + } + + static void assertEq(Integer i, int j) { + if (i == null) + assertEquals(j, -1); + else + assertEquals((int) i, j); + } + + static boolean eq(Integer i, int j) { + return (i == null) ? j == -1 : i == j; + } + +} diff --git a/jdk/test/java/util/concurrent/tck/ConcurrentSkipListSubMapTest.java b/jdk/test/java/util/concurrent/tck/ConcurrentSkipListSubMapTest.java new file mode 100644 index 00000000000..ddc82f78a56 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ConcurrentSkipListSubMapTest.java @@ -0,0 +1,1450 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.NavigableMap; +import java.util.Set; +import java.util.SortedMap; +import java.util.concurrent.ConcurrentNavigableMap; +import java.util.concurrent.ConcurrentSkipListMap; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ConcurrentSkipListSubMapTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(ConcurrentSkipListSubMapTest.class); + } + + /** + * Returns a new map from Integers 1-5 to Strings "A"-"E". + */ + private static ConcurrentNavigableMap map5() { + ConcurrentSkipListMap map = new ConcurrentSkipListMap(); + assertTrue(map.isEmpty()); + map.put(zero, "Z"); + map.put(one, "A"); + map.put(five, "E"); + map.put(three, "C"); + map.put(two, "B"); + map.put(four, "D"); + map.put(seven, "F"); + assertFalse(map.isEmpty()); + assertEquals(7, map.size()); + return map.subMap(one, true, seven, false); + } + + /** + * Returns a new map from Integers -5 to -1 to Strings "A"-"E". + */ + private static ConcurrentNavigableMap dmap5() { + ConcurrentSkipListMap map = new ConcurrentSkipListMap(); + assertTrue(map.isEmpty()); + map.put(m1, "A"); + map.put(m5, "E"); + map.put(m3, "C"); + map.put(m2, "B"); + map.put(m4, "D"); + assertFalse(map.isEmpty()); + assertEquals(5, map.size()); + return map.descendingMap(); + } + + private static ConcurrentNavigableMap map0() { + ConcurrentSkipListMap map = new ConcurrentSkipListMap(); + assertTrue(map.isEmpty()); + return map.tailMap(one, true); + } + + private static ConcurrentNavigableMap dmap0() { + ConcurrentSkipListMap map = new ConcurrentSkipListMap(); + assertTrue(map.isEmpty()); + return map; + } + + /** + * clear removes all pairs + */ + public void testClear() { + ConcurrentNavigableMap map = map5(); + map.clear(); + assertEquals(0, map.size()); + } + + /** + * Maps with same contents are equal + */ + public void testEquals() { + ConcurrentNavigableMap map1 = map5(); + ConcurrentNavigableMap map2 = map5(); + assertEquals(map1, map2); + assertEquals(map2, map1); + map1.clear(); + assertFalse(map1.equals(map2)); + assertFalse(map2.equals(map1)); + } + + /** + * containsKey returns true for contained key + */ + public void testContainsKey() { + ConcurrentNavigableMap map = map5(); + assertTrue(map.containsKey(one)); + assertFalse(map.containsKey(zero)); + } + + /** + * containsValue returns true for held values + */ + public void testContainsValue() { + ConcurrentNavigableMap map = map5(); + assertTrue(map.containsValue("A")); + assertFalse(map.containsValue("Z")); + } + + /** + * get returns the correct element at the given key, + * or null if not present + */ + public void testGet() { + ConcurrentNavigableMap map = map5(); + assertEquals("A", (String)map.get(one)); + ConcurrentNavigableMap empty = map0(); + assertNull(empty.get(one)); + } + + /** + * isEmpty is true of empty map and false for non-empty + */ + public void testIsEmpty() { + ConcurrentNavigableMap empty = map0(); + ConcurrentNavigableMap map = map5(); + assertTrue(empty.isEmpty()); + assertFalse(map.isEmpty()); + } + + /** + * firstKey returns first key + */ + public void testFirstKey() { + ConcurrentNavigableMap map = map5(); + assertEquals(one, map.firstKey()); + } + + /** + * lastKey returns last key + */ + public void testLastKey() { + ConcurrentNavigableMap map = map5(); + assertEquals(five, map.lastKey()); + } + + /** + * keySet returns a Set containing all the keys + */ + public void testKeySet() { + ConcurrentNavigableMap map = map5(); + Set s = map.keySet(); + assertEquals(5, s.size()); + assertTrue(s.contains(one)); + assertTrue(s.contains(two)); + assertTrue(s.contains(three)); + assertTrue(s.contains(four)); + assertTrue(s.contains(five)); + } + + /** + * keySet is ordered + */ + public void testKeySetOrder() { + ConcurrentNavigableMap map = map5(); + Set s = map.keySet(); + Iterator i = s.iterator(); + Integer last = (Integer)i.next(); + assertEquals(last, one); + while (i.hasNext()) { + Integer k = (Integer)i.next(); + assertTrue(last.compareTo(k) < 0); + last = k; + } + } + + /** + * values collection contains all values + */ + public void testValues() { + ConcurrentNavigableMap map = map5(); + Collection s = map.values(); + assertEquals(5, s.size()); + assertTrue(s.contains("A")); + assertTrue(s.contains("B")); + assertTrue(s.contains("C")); + assertTrue(s.contains("D")); + assertTrue(s.contains("E")); + } + + /** + * keySet.toArray returns contains all keys + */ + public void testKeySetToArray() { + ConcurrentNavigableMap map = map5(); + Set s = map.keySet(); + Object[] ar = s.toArray(); + assertTrue(s.containsAll(Arrays.asList(ar))); + assertEquals(5, ar.length); + ar[0] = m10; + assertFalse(s.containsAll(Arrays.asList(ar))); + } + + /** + * descendingkeySet.toArray returns contains all keys + */ + public void testDescendingKeySetToArray() { + ConcurrentNavigableMap map = map5(); + Set s = map.descendingKeySet(); + Object[] ar = s.toArray(); + assertEquals(5, ar.length); + assertTrue(s.containsAll(Arrays.asList(ar))); + ar[0] = m10; + assertFalse(s.containsAll(Arrays.asList(ar))); + } + + /** + * Values.toArray contains all values + */ + public void testValuesToArray() { + ConcurrentNavigableMap map = map5(); + Collection v = map.values(); + Object[] ar = v.toArray(); + ArrayList s = new ArrayList(Arrays.asList(ar)); + assertEquals(5, ar.length); + assertTrue(s.contains("A")); + assertTrue(s.contains("B")); + assertTrue(s.contains("C")); + assertTrue(s.contains("D")); + assertTrue(s.contains("E")); + } + + /** + * entrySet contains all pairs + */ + public void testEntrySet() { + ConcurrentNavigableMap map = map5(); + Set s = map.entrySet(); + assertEquals(5, s.size()); + Iterator it = s.iterator(); + while (it.hasNext()) { + Map.Entry e = (Map.Entry) it.next(); + assertTrue( + (e.getKey().equals(one) && e.getValue().equals("A")) || + (e.getKey().equals(two) && e.getValue().equals("B")) || + (e.getKey().equals(three) && e.getValue().equals("C")) || + (e.getKey().equals(four) && e.getValue().equals("D")) || + (e.getKey().equals(five) && e.getValue().equals("E"))); + } + } + + /** + * putAll adds all key-value pairs from the given map + */ + public void testPutAll() { + ConcurrentNavigableMap empty = map0(); + ConcurrentNavigableMap map = map5(); + empty.putAll(map); + assertEquals(5, empty.size()); + assertTrue(empty.containsKey(one)); + assertTrue(empty.containsKey(two)); + assertTrue(empty.containsKey(three)); + assertTrue(empty.containsKey(four)); + assertTrue(empty.containsKey(five)); + } + + /** + * putIfAbsent works when the given key is not present + */ + public void testPutIfAbsent() { + ConcurrentNavigableMap map = map5(); + map.putIfAbsent(six, "Z"); + assertTrue(map.containsKey(six)); + } + + /** + * putIfAbsent does not add the pair if the key is already present + */ + public void testPutIfAbsent2() { + ConcurrentNavigableMap map = map5(); + assertEquals("A", map.putIfAbsent(one, "Z")); + } + + /** + * replace fails when the given key is not present + */ + public void testReplace() { + ConcurrentNavigableMap map = map5(); + assertNull(map.replace(six, "Z")); + assertFalse(map.containsKey(six)); + } + + /** + * replace succeeds if the key is already present + */ + public void testReplace2() { + ConcurrentNavigableMap map = map5(); + assertNotNull(map.replace(one, "Z")); + assertEquals("Z", map.get(one)); + } + + /** + * replace value fails when the given key not mapped to expected value + */ + public void testReplaceValue() { + ConcurrentNavigableMap map = map5(); + assertEquals("A", map.get(one)); + assertFalse(map.replace(one, "Z", "Z")); + assertEquals("A", map.get(one)); + } + + /** + * replace value succeeds when the given key mapped to expected value + */ + public void testReplaceValue2() { + ConcurrentNavigableMap map = map5(); + assertEquals("A", map.get(one)); + assertTrue(map.replace(one, "A", "Z")); + assertEquals("Z", map.get(one)); + } + + /** + * remove removes the correct key-value pair from the map + */ + public void testRemove() { + ConcurrentNavigableMap map = map5(); + map.remove(five); + assertEquals(4, map.size()); + assertFalse(map.containsKey(five)); + } + + /** + * remove(key,value) removes only if pair present + */ + public void testRemove2() { + ConcurrentNavigableMap map = map5(); + assertTrue(map.containsKey(five)); + assertEquals("E", map.get(five)); + map.remove(five, "E"); + assertEquals(4, map.size()); + assertFalse(map.containsKey(five)); + map.remove(four, "A"); + assertEquals(4, map.size()); + assertTrue(map.containsKey(four)); + } + + /** + * lowerEntry returns preceding entry. + */ + public void testLowerEntry() { + ConcurrentNavigableMap map = map5(); + Map.Entry e1 = map.lowerEntry(three); + assertEquals(two, e1.getKey()); + + Map.Entry e2 = map.lowerEntry(six); + assertEquals(five, e2.getKey()); + + Map.Entry e3 = map.lowerEntry(one); + assertNull(e3); + + Map.Entry e4 = map.lowerEntry(zero); + assertNull(e4); + } + + /** + * higherEntry returns next entry. + */ + public void testHigherEntry() { + ConcurrentNavigableMap map = map5(); + Map.Entry e1 = map.higherEntry(three); + assertEquals(four, e1.getKey()); + + Map.Entry e2 = map.higherEntry(zero); + assertEquals(one, e2.getKey()); + + Map.Entry e3 = map.higherEntry(five); + assertNull(e3); + + Map.Entry e4 = map.higherEntry(six); + assertNull(e4); + } + + /** + * floorEntry returns preceding entry. + */ + public void testFloorEntry() { + ConcurrentNavigableMap map = map5(); + Map.Entry e1 = map.floorEntry(three); + assertEquals(three, e1.getKey()); + + Map.Entry e2 = map.floorEntry(six); + assertEquals(five, e2.getKey()); + + Map.Entry e3 = map.floorEntry(one); + assertEquals(one, e3.getKey()); + + Map.Entry e4 = map.floorEntry(zero); + assertNull(e4); + } + + /** + * ceilingEntry returns next entry. + */ + public void testCeilingEntry() { + ConcurrentNavigableMap map = map5(); + Map.Entry e1 = map.ceilingEntry(three); + assertEquals(three, e1.getKey()); + + Map.Entry e2 = map.ceilingEntry(zero); + assertEquals(one, e2.getKey()); + + Map.Entry e3 = map.ceilingEntry(five); + assertEquals(five, e3.getKey()); + + Map.Entry e4 = map.ceilingEntry(six); + assertNull(e4); + } + + /** + * pollFirstEntry returns entries in order + */ + public void testPollFirstEntry() { + ConcurrentNavigableMap map = map5(); + Map.Entry e = map.pollFirstEntry(); + assertEquals(one, e.getKey()); + assertEquals("A", e.getValue()); + e = map.pollFirstEntry(); + assertEquals(two, e.getKey()); + map.put(one, "A"); + e = map.pollFirstEntry(); + assertEquals(one, e.getKey()); + assertEquals("A", e.getValue()); + e = map.pollFirstEntry(); + assertEquals(three, e.getKey()); + map.remove(four); + e = map.pollFirstEntry(); + assertEquals(five, e.getKey()); + try { + e.setValue("A"); + shouldThrow(); + } catch (UnsupportedOperationException success) {} + e = map.pollFirstEntry(); + assertNull(e); + } + + /** + * pollLastEntry returns entries in order + */ + public void testPollLastEntry() { + ConcurrentNavigableMap map = map5(); + Map.Entry e = map.pollLastEntry(); + assertEquals(five, e.getKey()); + assertEquals("E", e.getValue()); + e = map.pollLastEntry(); + assertEquals(four, e.getKey()); + map.put(five, "E"); + e = map.pollLastEntry(); + assertEquals(five, e.getKey()); + assertEquals("E", e.getValue()); + e = map.pollLastEntry(); + assertEquals(three, e.getKey()); + map.remove(two); + e = map.pollLastEntry(); + assertEquals(one, e.getKey()); + try { + e.setValue("E"); + shouldThrow(); + } catch (UnsupportedOperationException success) {} + e = map.pollLastEntry(); + assertNull(e); + } + + /** + * size returns the correct values + */ + public void testSize() { + ConcurrentNavigableMap map = map5(); + ConcurrentNavigableMap empty = map0(); + assertEquals(0, empty.size()); + assertEquals(5, map.size()); + } + + /** + * toString contains toString of elements + */ + public void testToString() { + ConcurrentNavigableMap map = map5(); + String s = map.toString(); + for (int i = 1; i <= 5; ++i) { + assertTrue(s.contains(String.valueOf(i))); + } + } + + // Exception tests + + /** + * get(null) of nonempty map throws NPE + */ + public void testGet_NullPointerException() { + try { + ConcurrentNavigableMap c = map5(); + c.get(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * containsKey(null) of nonempty map throws NPE + */ + public void testContainsKey_NullPointerException() { + try { + ConcurrentNavigableMap c = map5(); + c.containsKey(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * containsValue(null) throws NPE + */ + public void testContainsValue_NullPointerException() { + try { + ConcurrentNavigableMap c = map0(); + c.containsValue(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * put(null,x) throws NPE + */ + public void testPut1_NullPointerException() { + try { + ConcurrentNavigableMap c = map5(); + c.put(null, "whatever"); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * putIfAbsent(null, x) throws NPE + */ + public void testPutIfAbsent1_NullPointerException() { + try { + ConcurrentNavigableMap c = map5(); + c.putIfAbsent(null, "whatever"); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * replace(null, x) throws NPE + */ + public void testReplace_NullPointerException() { + try { + ConcurrentNavigableMap c = map5(); + c.replace(null, "whatever"); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * replace(null, x, y) throws NPE + */ + public void testReplaceValue_NullPointerException() { + try { + ConcurrentNavigableMap c = map5(); + c.replace(null, one, "whatever"); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * remove(null) throws NPE + */ + public void testRemove1_NullPointerException() { + try { + ConcurrentNavigableMap c = map5(); + c.remove(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * remove(null, x) throws NPE + */ + public void testRemove2_NullPointerException() { + try { + ConcurrentNavigableMap c = map5(); + c.remove(null, "whatever"); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * A deserialized map equals original + */ + public void testSerialization() throws Exception { + NavigableMap x = map5(); + NavigableMap y = serialClone(x); + + assertNotSame(x, y); + assertEquals(x.size(), y.size()); + assertEquals(x.toString(), y.toString()); + assertEquals(x, y); + assertEquals(y, x); + } + + /** + * subMap returns map with keys in requested range + */ + public void testSubMapContents() { + ConcurrentNavigableMap map = map5(); + SortedMap sm = map.subMap(two, four); + assertEquals(two, sm.firstKey()); + assertEquals(three, sm.lastKey()); + assertEquals(2, sm.size()); + assertFalse(sm.containsKey(one)); + assertTrue(sm.containsKey(two)); + assertTrue(sm.containsKey(three)); + assertFalse(sm.containsKey(four)); + assertFalse(sm.containsKey(five)); + Iterator i = sm.keySet().iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(two, k); + k = (Integer)(i.next()); + assertEquals(three, k); + assertFalse(i.hasNext()); + Iterator j = sm.keySet().iterator(); + j.next(); + j.remove(); + assertFalse(map.containsKey(two)); + assertEquals(4, map.size()); + assertEquals(1, sm.size()); + assertEquals(three, sm.firstKey()); + assertEquals(three, sm.lastKey()); + assertEquals("C", sm.remove(three)); + assertTrue(sm.isEmpty()); + assertEquals(3, map.size()); + } + + public void testSubMapContents2() { + ConcurrentNavigableMap map = map5(); + SortedMap sm = map.subMap(two, three); + assertEquals(1, sm.size()); + assertEquals(two, sm.firstKey()); + assertEquals(two, sm.lastKey()); + assertFalse(sm.containsKey(one)); + assertTrue(sm.containsKey(two)); + assertFalse(sm.containsKey(three)); + assertFalse(sm.containsKey(four)); + assertFalse(sm.containsKey(five)); + Iterator i = sm.keySet().iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(two, k); + assertFalse(i.hasNext()); + Iterator j = sm.keySet().iterator(); + j.next(); + j.remove(); + assertFalse(map.containsKey(two)); + assertEquals(4, map.size()); + assertEquals(0, sm.size()); + assertTrue(sm.isEmpty()); + assertSame(sm.remove(three), null); + assertEquals(4, map.size()); + } + + /** + * headMap returns map with keys in requested range + */ + public void testHeadMapContents() { + ConcurrentNavigableMap map = map5(); + SortedMap sm = map.headMap(four); + assertTrue(sm.containsKey(one)); + assertTrue(sm.containsKey(two)); + assertTrue(sm.containsKey(three)); + assertFalse(sm.containsKey(four)); + assertFalse(sm.containsKey(five)); + Iterator i = sm.keySet().iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(one, k); + k = (Integer)(i.next()); + assertEquals(two, k); + k = (Integer)(i.next()); + assertEquals(three, k); + assertFalse(i.hasNext()); + sm.clear(); + assertTrue(sm.isEmpty()); + assertEquals(2, map.size()); + assertEquals(four, map.firstKey()); + } + + /** + * headMap returns map with keys in requested range + */ + public void testTailMapContents() { + ConcurrentNavigableMap map = map5(); + SortedMap sm = map.tailMap(two); + assertFalse(sm.containsKey(one)); + assertTrue(sm.containsKey(two)); + assertTrue(sm.containsKey(three)); + assertTrue(sm.containsKey(four)); + assertTrue(sm.containsKey(five)); + Iterator i = sm.keySet().iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(two, k); + k = (Integer)(i.next()); + assertEquals(three, k); + k = (Integer)(i.next()); + assertEquals(four, k); + k = (Integer)(i.next()); + assertEquals(five, k); + assertFalse(i.hasNext()); + + Iterator ei = sm.entrySet().iterator(); + Map.Entry e; + e = (Map.Entry)(ei.next()); + assertEquals(two, e.getKey()); + assertEquals("B", e.getValue()); + e = (Map.Entry)(ei.next()); + assertEquals(three, e.getKey()); + assertEquals("C", e.getValue()); + e = (Map.Entry)(ei.next()); + assertEquals(four, e.getKey()); + assertEquals("D", e.getValue()); + e = (Map.Entry)(ei.next()); + assertEquals(five, e.getKey()); + assertEquals("E", e.getValue()); + assertFalse(i.hasNext()); + + SortedMap ssm = sm.tailMap(four); + assertEquals(four, ssm.firstKey()); + assertEquals(five, ssm.lastKey()); + assertEquals("D", ssm.remove(four)); + assertEquals(1, ssm.size()); + assertEquals(3, sm.size()); + assertEquals(4, map.size()); + } + + /** + * clear removes all pairs + */ + public void testDescendingClear() { + ConcurrentNavigableMap map = dmap5(); + map.clear(); + assertEquals(0, map.size()); + } + + /** + * Maps with same contents are equal + */ + public void testDescendingEquals() { + ConcurrentNavigableMap map1 = dmap5(); + ConcurrentNavigableMap map2 = dmap5(); + assertEquals(map1, map2); + assertEquals(map2, map1); + map1.clear(); + assertFalse(map1.equals(map2)); + assertFalse(map2.equals(map1)); + } + + /** + * containsKey returns true for contained key + */ + public void testDescendingContainsKey() { + ConcurrentNavigableMap map = dmap5(); + assertTrue(map.containsKey(m1)); + assertFalse(map.containsKey(zero)); + } + + /** + * containsValue returns true for held values + */ + public void testDescendingContainsValue() { + ConcurrentNavigableMap map = dmap5(); + assertTrue(map.containsValue("A")); + assertFalse(map.containsValue("Z")); + } + + /** + * get returns the correct element at the given key, + * or null if not present + */ + public void testDescendingGet() { + ConcurrentNavigableMap map = dmap5(); + assertEquals("A", (String)map.get(m1)); + ConcurrentNavigableMap empty = dmap0(); + assertNull(empty.get(m1)); + } + + /** + * isEmpty is true of empty map and false for non-empty + */ + public void testDescendingIsEmpty() { + ConcurrentNavigableMap empty = dmap0(); + ConcurrentNavigableMap map = dmap5(); + assertTrue(empty.isEmpty()); + assertFalse(map.isEmpty()); + } + + /** + * firstKey returns first key + */ + public void testDescendingFirstKey() { + ConcurrentNavigableMap map = dmap5(); + assertEquals(m1, map.firstKey()); + } + + /** + * lastKey returns last key + */ + public void testDescendingLastKey() { + ConcurrentNavigableMap map = dmap5(); + assertEquals(m5, map.lastKey()); + } + + /** + * keySet returns a Set containing all the keys + */ + public void testDescendingKeySet() { + ConcurrentNavigableMap map = dmap5(); + Set s = map.keySet(); + assertEquals(5, s.size()); + assertTrue(s.contains(m1)); + assertTrue(s.contains(m2)); + assertTrue(s.contains(m3)); + assertTrue(s.contains(m4)); + assertTrue(s.contains(m5)); + } + + /** + * keySet is ordered + */ + public void testDescendingKeySetOrder() { + ConcurrentNavigableMap map = dmap5(); + Set s = map.keySet(); + Iterator i = s.iterator(); + Integer last = (Integer)i.next(); + assertEquals(last, m1); + while (i.hasNext()) { + Integer k = (Integer)i.next(); + assertTrue(last.compareTo(k) > 0); + last = k; + } + } + + /** + * values collection contains all values + */ + public void testDescendingValues() { + ConcurrentNavigableMap map = dmap5(); + Collection s = map.values(); + assertEquals(5, s.size()); + assertTrue(s.contains("A")); + assertTrue(s.contains("B")); + assertTrue(s.contains("C")); + assertTrue(s.contains("D")); + assertTrue(s.contains("E")); + } + + /** + * keySet.toArray returns contains all keys + */ + public void testDescendingAscendingKeySetToArray() { + ConcurrentNavigableMap map = dmap5(); + Set s = map.keySet(); + Object[] ar = s.toArray(); + assertTrue(s.containsAll(Arrays.asList(ar))); + assertEquals(5, ar.length); + ar[0] = m10; + assertFalse(s.containsAll(Arrays.asList(ar))); + } + + /** + * descendingkeySet.toArray returns contains all keys + */ + public void testDescendingDescendingKeySetToArray() { + ConcurrentNavigableMap map = dmap5(); + Set s = map.descendingKeySet(); + Object[] ar = s.toArray(); + assertEquals(5, ar.length); + assertTrue(s.containsAll(Arrays.asList(ar))); + ar[0] = m10; + assertFalse(s.containsAll(Arrays.asList(ar))); + } + + /** + * Values.toArray contains all values + */ + public void testDescendingValuesToArray() { + ConcurrentNavigableMap map = dmap5(); + Collection v = map.values(); + Object[] ar = v.toArray(); + ArrayList s = new ArrayList(Arrays.asList(ar)); + assertEquals(5, ar.length); + assertTrue(s.contains("A")); + assertTrue(s.contains("B")); + assertTrue(s.contains("C")); + assertTrue(s.contains("D")); + assertTrue(s.contains("E")); + } + + /** + * entrySet contains all pairs + */ + public void testDescendingEntrySet() { + ConcurrentNavigableMap map = dmap5(); + Set s = map.entrySet(); + assertEquals(5, s.size()); + Iterator it = s.iterator(); + while (it.hasNext()) { + Map.Entry e = (Map.Entry) it.next(); + assertTrue( + (e.getKey().equals(m1) && e.getValue().equals("A")) || + (e.getKey().equals(m2) && e.getValue().equals("B")) || + (e.getKey().equals(m3) && e.getValue().equals("C")) || + (e.getKey().equals(m4) && e.getValue().equals("D")) || + (e.getKey().equals(m5) && e.getValue().equals("E"))); + } + } + + /** + * putAll adds all key-value pairs from the given map + */ + public void testDescendingPutAll() { + ConcurrentNavigableMap empty = dmap0(); + ConcurrentNavigableMap map = dmap5(); + empty.putAll(map); + assertEquals(5, empty.size()); + assertTrue(empty.containsKey(m1)); + assertTrue(empty.containsKey(m2)); + assertTrue(empty.containsKey(m3)); + assertTrue(empty.containsKey(m4)); + assertTrue(empty.containsKey(m5)); + } + + /** + * putIfAbsent works when the given key is not present + */ + public void testDescendingPutIfAbsent() { + ConcurrentNavigableMap map = dmap5(); + map.putIfAbsent(six, "Z"); + assertTrue(map.containsKey(six)); + } + + /** + * putIfAbsent does not add the pair if the key is already present + */ + public void testDescendingPutIfAbsent2() { + ConcurrentNavigableMap map = dmap5(); + assertEquals("A", map.putIfAbsent(m1, "Z")); + } + + /** + * replace fails when the given key is not present + */ + public void testDescendingReplace() { + ConcurrentNavigableMap map = dmap5(); + assertNull(map.replace(six, "Z")); + assertFalse(map.containsKey(six)); + } + + /** + * replace succeeds if the key is already present + */ + public void testDescendingReplace2() { + ConcurrentNavigableMap map = dmap5(); + assertNotNull(map.replace(m1, "Z")); + assertEquals("Z", map.get(m1)); + } + + /** + * replace value fails when the given key not mapped to expected value + */ + public void testDescendingReplaceValue() { + ConcurrentNavigableMap map = dmap5(); + assertEquals("A", map.get(m1)); + assertFalse(map.replace(m1, "Z", "Z")); + assertEquals("A", map.get(m1)); + } + + /** + * replace value succeeds when the given key mapped to expected value + */ + public void testDescendingReplaceValue2() { + ConcurrentNavigableMap map = dmap5(); + assertEquals("A", map.get(m1)); + assertTrue(map.replace(m1, "A", "Z")); + assertEquals("Z", map.get(m1)); + } + + /** + * remove removes the correct key-value pair from the map + */ + public void testDescendingRemove() { + ConcurrentNavigableMap map = dmap5(); + map.remove(m5); + assertEquals(4, map.size()); + assertFalse(map.containsKey(m5)); + } + + /** + * remove(key,value) removes only if pair present + */ + public void testDescendingRemove2() { + ConcurrentNavigableMap map = dmap5(); + assertTrue(map.containsKey(m5)); + assertEquals("E", map.get(m5)); + map.remove(m5, "E"); + assertEquals(4, map.size()); + assertFalse(map.containsKey(m5)); + map.remove(m4, "A"); + assertEquals(4, map.size()); + assertTrue(map.containsKey(m4)); + } + + /** + * lowerEntry returns preceding entry. + */ + public void testDescendingLowerEntry() { + ConcurrentNavigableMap map = dmap5(); + Map.Entry e1 = map.lowerEntry(m3); + assertEquals(m2, e1.getKey()); + + Map.Entry e2 = map.lowerEntry(m6); + assertEquals(m5, e2.getKey()); + + Map.Entry e3 = map.lowerEntry(m1); + assertNull(e3); + + Map.Entry e4 = map.lowerEntry(zero); + assertNull(e4); + } + + /** + * higherEntry returns next entry. + */ + public void testDescendingHigherEntry() { + ConcurrentNavigableMap map = dmap5(); + Map.Entry e1 = map.higherEntry(m3); + assertEquals(m4, e1.getKey()); + + Map.Entry e2 = map.higherEntry(zero); + assertEquals(m1, e2.getKey()); + + Map.Entry e3 = map.higherEntry(m5); + assertNull(e3); + + Map.Entry e4 = map.higherEntry(m6); + assertNull(e4); + } + + /** + * floorEntry returns preceding entry. + */ + public void testDescendingFloorEntry() { + ConcurrentNavigableMap map = dmap5(); + Map.Entry e1 = map.floorEntry(m3); + assertEquals(m3, e1.getKey()); + + Map.Entry e2 = map.floorEntry(m6); + assertEquals(m5, e2.getKey()); + + Map.Entry e3 = map.floorEntry(m1); + assertEquals(m1, e3.getKey()); + + Map.Entry e4 = map.floorEntry(zero); + assertNull(e4); + } + + /** + * ceilingEntry returns next entry. + */ + public void testDescendingCeilingEntry() { + ConcurrentNavigableMap map = dmap5(); + Map.Entry e1 = map.ceilingEntry(m3); + assertEquals(m3, e1.getKey()); + + Map.Entry e2 = map.ceilingEntry(zero); + assertEquals(m1, e2.getKey()); + + Map.Entry e3 = map.ceilingEntry(m5); + assertEquals(m5, e3.getKey()); + + Map.Entry e4 = map.ceilingEntry(m6); + assertNull(e4); + } + + /** + * pollFirstEntry returns entries in order + */ + public void testDescendingPollFirstEntry() { + ConcurrentNavigableMap map = dmap5(); + Map.Entry e = map.pollFirstEntry(); + assertEquals(m1, e.getKey()); + assertEquals("A", e.getValue()); + e = map.pollFirstEntry(); + assertEquals(m2, e.getKey()); + map.put(m1, "A"); + e = map.pollFirstEntry(); + assertEquals(m1, e.getKey()); + assertEquals("A", e.getValue()); + e = map.pollFirstEntry(); + assertEquals(m3, e.getKey()); + map.remove(m4); + e = map.pollFirstEntry(); + assertEquals(m5, e.getKey()); + try { + e.setValue("A"); + shouldThrow(); + } catch (UnsupportedOperationException success) {} + e = map.pollFirstEntry(); + assertNull(e); + } + + /** + * pollLastEntry returns entries in order + */ + public void testDescendingPollLastEntry() { + ConcurrentNavigableMap map = dmap5(); + Map.Entry e = map.pollLastEntry(); + assertEquals(m5, e.getKey()); + assertEquals("E", e.getValue()); + e = map.pollLastEntry(); + assertEquals(m4, e.getKey()); + map.put(m5, "E"); + e = map.pollLastEntry(); + assertEquals(m5, e.getKey()); + assertEquals("E", e.getValue()); + e = map.pollLastEntry(); + assertEquals(m3, e.getKey()); + map.remove(m2); + e = map.pollLastEntry(); + assertEquals(m1, e.getKey()); + try { + e.setValue("E"); + shouldThrow(); + } catch (UnsupportedOperationException success) {} + e = map.pollLastEntry(); + assertNull(e); + } + + /** + * size returns the correct values + */ + public void testDescendingSize() { + ConcurrentNavigableMap map = dmap5(); + ConcurrentNavigableMap empty = dmap0(); + assertEquals(0, empty.size()); + assertEquals(5, map.size()); + } + + /** + * toString contains toString of elements + */ + public void testDescendingToString() { + ConcurrentNavigableMap map = dmap5(); + String s = map.toString(); + for (int i = 1; i <= 5; ++i) { + assertTrue(s.contains(String.valueOf(i))); + } + } + + // Exception testDescendings + + /** + * get(null) of empty map throws NPE + */ + public void testDescendingGet_NullPointerException() { + try { + ConcurrentNavigableMap c = dmap5(); + c.get(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * containsKey(null) of empty map throws NPE + */ + public void testDescendingContainsKey_NullPointerException() { + try { + ConcurrentNavigableMap c = dmap5(); + c.containsKey(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * containsValue(null) throws NPE + */ + public void testDescendingContainsValue_NullPointerException() { + try { + ConcurrentNavigableMap c = dmap0(); + c.containsValue(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * put(null,x) throws NPE + */ + public void testDescendingPut1_NullPointerException() { + try { + ConcurrentNavigableMap c = dmap5(); + c.put(null, "whatever"); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * putIfAbsent(null, x) throws NPE + */ + public void testDescendingPutIfAbsent1_NullPointerException() { + try { + ConcurrentNavigableMap c = dmap5(); + c.putIfAbsent(null, "whatever"); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * replace(null, x) throws NPE + */ + public void testDescendingReplace_NullPointerException() { + try { + ConcurrentNavigableMap c = dmap5(); + c.replace(null, "whatever"); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * replace(null, x, y) throws NPE + */ + public void testDescendingReplaceValue_NullPointerException() { + try { + ConcurrentNavigableMap c = dmap5(); + c.replace(null, m1, "whatever"); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * remove(null) throws NPE + */ + public void testDescendingRemove1_NullPointerException() { + try { + ConcurrentNavigableMap c = dmap5(); + c.remove(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * remove(null, x) throws NPE + */ + public void testDescendingRemove2_NullPointerException() { + try { + ConcurrentNavigableMap c = dmap5(); + c.remove(null, "whatever"); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * A deserialized map equals original + */ + public void testDescendingSerialization() throws Exception { + NavigableMap x = dmap5(); + NavigableMap y = serialClone(x); + + assertNotSame(x, y); + assertEquals(x.size(), y.size()); + assertEquals(x.toString(), y.toString()); + assertEquals(x, y); + assertEquals(y, x); + } + + /** + * subMap returns map with keys in requested range + */ + public void testDescendingSubMapContents() { + ConcurrentNavigableMap map = dmap5(); + SortedMap sm = map.subMap(m2, m4); + assertEquals(m2, sm.firstKey()); + assertEquals(m3, sm.lastKey()); + assertEquals(2, sm.size()); + assertFalse(sm.containsKey(m1)); + assertTrue(sm.containsKey(m2)); + assertTrue(sm.containsKey(m3)); + assertFalse(sm.containsKey(m4)); + assertFalse(sm.containsKey(m5)); + Iterator i = sm.keySet().iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(m2, k); + k = (Integer)(i.next()); + assertEquals(m3, k); + assertFalse(i.hasNext()); + Iterator j = sm.keySet().iterator(); + j.next(); + j.remove(); + assertFalse(map.containsKey(m2)); + assertEquals(4, map.size()); + assertEquals(1, sm.size()); + assertEquals(m3, sm.firstKey()); + assertEquals(m3, sm.lastKey()); + assertEquals("C", sm.remove(m3)); + assertTrue(sm.isEmpty()); + assertEquals(3, map.size()); + } + + public void testDescendingSubMapContents2() { + ConcurrentNavigableMap map = dmap5(); + SortedMap sm = map.subMap(m2, m3); + assertEquals(1, sm.size()); + assertEquals(m2, sm.firstKey()); + assertEquals(m2, sm.lastKey()); + assertFalse(sm.containsKey(m1)); + assertTrue(sm.containsKey(m2)); + assertFalse(sm.containsKey(m3)); + assertFalse(sm.containsKey(m4)); + assertFalse(sm.containsKey(m5)); + Iterator i = sm.keySet().iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(m2, k); + assertFalse(i.hasNext()); + Iterator j = sm.keySet().iterator(); + j.next(); + j.remove(); + assertFalse(map.containsKey(m2)); + assertEquals(4, map.size()); + assertEquals(0, sm.size()); + assertTrue(sm.isEmpty()); + assertSame(sm.remove(m3), null); + assertEquals(4, map.size()); + } + + /** + * headMap returns map with keys in requested range + */ + public void testDescendingHeadMapContents() { + ConcurrentNavigableMap map = dmap5(); + SortedMap sm = map.headMap(m4); + assertTrue(sm.containsKey(m1)); + assertTrue(sm.containsKey(m2)); + assertTrue(sm.containsKey(m3)); + assertFalse(sm.containsKey(m4)); + assertFalse(sm.containsKey(m5)); + Iterator i = sm.keySet().iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(m1, k); + k = (Integer)(i.next()); + assertEquals(m2, k); + k = (Integer)(i.next()); + assertEquals(m3, k); + assertFalse(i.hasNext()); + sm.clear(); + assertTrue(sm.isEmpty()); + assertEquals(2, map.size()); + assertEquals(m4, map.firstKey()); + } + + /** + * headMap returns map with keys in requested range + */ + public void testDescendingTailMapContents() { + ConcurrentNavigableMap map = dmap5(); + SortedMap sm = map.tailMap(m2); + assertFalse(sm.containsKey(m1)); + assertTrue(sm.containsKey(m2)); + assertTrue(sm.containsKey(m3)); + assertTrue(sm.containsKey(m4)); + assertTrue(sm.containsKey(m5)); + Iterator i = sm.keySet().iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(m2, k); + k = (Integer)(i.next()); + assertEquals(m3, k); + k = (Integer)(i.next()); + assertEquals(m4, k); + k = (Integer)(i.next()); + assertEquals(m5, k); + assertFalse(i.hasNext()); + + Iterator ei = sm.entrySet().iterator(); + Map.Entry e; + e = (Map.Entry)(ei.next()); + assertEquals(m2, e.getKey()); + assertEquals("B", e.getValue()); + e = (Map.Entry)(ei.next()); + assertEquals(m3, e.getKey()); + assertEquals("C", e.getValue()); + e = (Map.Entry)(ei.next()); + assertEquals(m4, e.getKey()); + assertEquals("D", e.getValue()); + e = (Map.Entry)(ei.next()); + assertEquals(m5, e.getKey()); + assertEquals("E", e.getValue()); + assertFalse(i.hasNext()); + + SortedMap ssm = sm.tailMap(m4); + assertEquals(m4, ssm.firstKey()); + assertEquals(m5, ssm.lastKey()); + assertEquals("D", ssm.remove(m4)); + assertEquals(1, ssm.size()); + assertEquals(3, sm.size()); + assertEquals(4, map.size()); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/ConcurrentSkipListSubSetTest.java b/jdk/test/java/util/concurrent/tck/ConcurrentSkipListSubSetTest.java new file mode 100644 index 00000000000..bad618f3fab --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ConcurrentSkipListSubSetTest.java @@ -0,0 +1,1141 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import java.util.Arrays; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NavigableSet; +import java.util.SortedSet; +import java.util.concurrent.ConcurrentSkipListSet; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ConcurrentSkipListSubSetTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(ConcurrentSkipListSubSetTest.class); + } + + static class MyReverseComparator implements Comparator { + public int compare(Object x, Object y) { + return ((Comparable)y).compareTo(x); + } + } + + /** + * Returns a new set of given size containing consecutive + * Integers 0 ... n. + */ + private NavigableSet populatedSet(int n) { + ConcurrentSkipListSet q = + new ConcurrentSkipListSet(); + assertTrue(q.isEmpty()); + + for (int i = n - 1; i >= 0; i -= 2) + assertTrue(q.add(new Integer(i))); + for (int i = (n & 1); i < n; i += 2) + assertTrue(q.add(new Integer(i))); + assertTrue(q.add(new Integer(-n))); + assertTrue(q.add(new Integer(n))); + NavigableSet s = q.subSet(new Integer(0), true, new Integer(n), false); + assertFalse(s.isEmpty()); + assertEquals(n, s.size()); + return s; + } + + /** + * Returns a new set of first 5 ints. + */ + private NavigableSet set5() { + ConcurrentSkipListSet q = new ConcurrentSkipListSet(); + assertTrue(q.isEmpty()); + q.add(one); + q.add(two); + q.add(three); + q.add(four); + q.add(five); + q.add(zero); + q.add(seven); + NavigableSet s = q.subSet(one, true, seven, false); + assertEquals(5, s.size()); + return s; + } + + /** + * Returns a new set of first 5 negative ints. + */ + private NavigableSet dset5() { + ConcurrentSkipListSet q = new ConcurrentSkipListSet(); + assertTrue(q.isEmpty()); + q.add(m1); + q.add(m2); + q.add(m3); + q.add(m4); + q.add(m5); + NavigableSet s = q.descendingSet(); + assertEquals(5, s.size()); + return s; + } + + private static NavigableSet set0() { + ConcurrentSkipListSet set = new ConcurrentSkipListSet(); + assertTrue(set.isEmpty()); + return set.tailSet(m1, true); + } + + private static NavigableSet dset0() { + ConcurrentSkipListSet set = new ConcurrentSkipListSet(); + assertTrue(set.isEmpty()); + return set; + } + + /** + * A new set has unbounded capacity + */ + public void testConstructor1() { + assertEquals(0, set0().size()); + } + + /** + * isEmpty is true before add, false after + */ + public void testEmpty() { + NavigableSet q = set0(); + assertTrue(q.isEmpty()); + q.add(new Integer(1)); + assertFalse(q.isEmpty()); + q.add(new Integer(2)); + q.pollFirst(); + q.pollFirst(); + assertTrue(q.isEmpty()); + } + + /** + * size changes when elements added and removed + */ + public void testSize() { + NavigableSet q = populatedSet(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(SIZE - i, q.size()); + q.pollFirst(); + } + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.size()); + q.add(new Integer(i)); + } + } + + /** + * add(null) throws NPE + */ + public void testAddNull() { + NavigableSet q = set0(); + try { + q.add(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Add of comparable element succeeds + */ + public void testAdd() { + NavigableSet q = set0(); + assertTrue(q.add(six)); + } + + /** + * Add of duplicate element fails + */ + public void testAddDup() { + NavigableSet q = set0(); + assertTrue(q.add(six)); + assertFalse(q.add(six)); + } + + /** + * Add of non-Comparable throws CCE + */ + public void testAddNonComparable() { + NavigableSet q = set0(); + try { + q.add(new Object()); + q.add(new Object()); + shouldThrow(); + } catch (ClassCastException success) {} + } + + /** + * addAll(null) throws NPE + */ + public void testAddAll1() { + NavigableSet q = set0(); + try { + q.addAll(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll of a collection with null elements throws NPE + */ + public void testAddAll2() { + NavigableSet q = set0(); + Integer[] ints = new Integer[SIZE]; + try { + q.addAll(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll of a collection with any null elements throws NPE after + * possibly adding some elements + */ + public void testAddAll3() { + NavigableSet q = set0(); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i + SIZE); + try { + q.addAll(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Set contains all elements of successful addAll + */ + public void testAddAll5() { + Integer[] empty = new Integer[0]; + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(SIZE - 1 - i); + NavigableSet q = set0(); + assertFalse(q.addAll(Arrays.asList(empty))); + assertTrue(q.addAll(Arrays.asList(ints))); + for (int i = 0; i < SIZE; ++i) + assertEquals(new Integer(i), q.pollFirst()); + } + + /** + * poll succeeds unless empty + */ + public void testPoll() { + NavigableSet q = populatedSet(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.pollFirst()); + } + assertNull(q.pollFirst()); + } + + /** + * remove(x) removes x and returns true if present + */ + public void testRemoveElement() { + NavigableSet q = populatedSet(SIZE); + for (int i = 1; i < SIZE; i += 2) { + assertTrue(q.contains(i)); + assertTrue(q.remove(i)); + assertFalse(q.contains(i)); + assertTrue(q.contains(i - 1)); + } + for (int i = 0; i < SIZE; i += 2) { + assertTrue(q.contains(i)); + assertTrue(q.remove(i)); + assertFalse(q.contains(i)); + assertFalse(q.remove(i + 1)); + assertFalse(q.contains(i + 1)); + } + assertTrue(q.isEmpty()); + } + + /** + * contains(x) reports true when elements added but not yet removed + */ + public void testContains() { + NavigableSet q = populatedSet(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.contains(new Integer(i))); + q.pollFirst(); + assertFalse(q.contains(new Integer(i))); + } + } + + /** + * clear removes all elements + */ + public void testClear() { + NavigableSet q = populatedSet(SIZE); + q.clear(); + assertTrue(q.isEmpty()); + assertEquals(0, q.size()); + q.add(new Integer(1)); + assertFalse(q.isEmpty()); + q.clear(); + assertTrue(q.isEmpty()); + } + + /** + * containsAll(c) is true when c contains a subset of elements + */ + public void testContainsAll() { + NavigableSet q = populatedSet(SIZE); + NavigableSet p = set0(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.containsAll(p)); + assertFalse(p.containsAll(q)); + p.add(new Integer(i)); + } + assertTrue(p.containsAll(q)); + } + + /** + * retainAll(c) retains only those elements of c and reports true if changed + */ + public void testRetainAll() { + NavigableSet q = populatedSet(SIZE); + NavigableSet p = populatedSet(SIZE); + for (int i = 0; i < SIZE; ++i) { + boolean changed = q.retainAll(p); + if (i == 0) + assertFalse(changed); + else + assertTrue(changed); + + assertTrue(q.containsAll(p)); + assertEquals(SIZE - i, q.size()); + p.pollFirst(); + } + } + + /** + * removeAll(c) removes only those elements of c and reports true if changed + */ + public void testRemoveAll() { + for (int i = 1; i < SIZE; ++i) { + NavigableSet q = populatedSet(SIZE); + NavigableSet p = populatedSet(i); + assertTrue(q.removeAll(p)); + assertEquals(SIZE - i, q.size()); + for (int j = 0; j < i; ++j) { + Integer x = (Integer)(p.pollFirst()); + assertFalse(q.contains(x)); + } + } + } + + /** + * lower returns preceding element + */ + public void testLower() { + NavigableSet q = set5(); + Object e1 = q.lower(three); + assertEquals(two, e1); + + Object e2 = q.lower(six); + assertEquals(five, e2); + + Object e3 = q.lower(one); + assertNull(e3); + + Object e4 = q.lower(zero); + assertNull(e4); + } + + /** + * higher returns next element + */ + public void testHigher() { + NavigableSet q = set5(); + Object e1 = q.higher(three); + assertEquals(four, e1); + + Object e2 = q.higher(zero); + assertEquals(one, e2); + + Object e3 = q.higher(five); + assertNull(e3); + + Object e4 = q.higher(six); + assertNull(e4); + } + + /** + * floor returns preceding element + */ + public void testFloor() { + NavigableSet q = set5(); + Object e1 = q.floor(three); + assertEquals(three, e1); + + Object e2 = q.floor(six); + assertEquals(five, e2); + + Object e3 = q.floor(one); + assertEquals(one, e3); + + Object e4 = q.floor(zero); + assertNull(e4); + } + + /** + * ceiling returns next element + */ + public void testCeiling() { + NavigableSet q = set5(); + Object e1 = q.ceiling(three); + assertEquals(three, e1); + + Object e2 = q.ceiling(zero); + assertEquals(one, e2); + + Object e3 = q.ceiling(five); + assertEquals(five, e3); + + Object e4 = q.ceiling(six); + assertNull(e4); + } + + /** + * toArray contains all elements in sorted order + */ + public void testToArray() { + NavigableSet q = populatedSet(SIZE); + Object[] o = q.toArray(); + for (int i = 0; i < o.length; i++) + assertSame(o[i], q.pollFirst()); + } + + /** + * toArray(a) contains all elements in sorted order + */ + public void testToArray2() { + NavigableSet q = populatedSet(SIZE); + Integer[] ints = new Integer[SIZE]; + Integer[] array = q.toArray(ints); + assertSame(ints, array); + for (int i = 0; i < ints.length; i++) + assertSame(ints[i], q.pollFirst()); + } + + /** + * iterator iterates through all elements + */ + public void testIterator() { + NavigableSet q = populatedSet(SIZE); + Iterator it = q.iterator(); + int i; + for (i = 0; it.hasNext(); i++) + assertTrue(q.contains(it.next())); + assertEquals(i, SIZE); + assertIteratorExhausted(it); + } + + /** + * iterator of empty set has no elements + */ + public void testEmptyIterator() { + assertIteratorExhausted(set0().iterator()); + } + + /** + * iterator.remove removes current element + */ + public void testIteratorRemove() { + final NavigableSet q = set0(); + q.add(new Integer(2)); + q.add(new Integer(1)); + q.add(new Integer(3)); + + Iterator it = q.iterator(); + it.next(); + it.remove(); + + it = q.iterator(); + assertEquals(it.next(), new Integer(2)); + assertEquals(it.next(), new Integer(3)); + assertFalse(it.hasNext()); + } + + /** + * toString contains toStrings of elements + */ + public void testToString() { + NavigableSet q = populatedSet(SIZE); + String s = q.toString(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(s.contains(String.valueOf(i))); + } + } + + /** + * A deserialized serialized set has same elements + */ + public void testSerialization() throws Exception { + NavigableSet x = populatedSet(SIZE); + NavigableSet y = serialClone(x); + + assertNotSame(y, x); + assertEquals(x.size(), y.size()); + assertEquals(x, y); + assertEquals(y, x); + while (!x.isEmpty()) { + assertFalse(y.isEmpty()); + assertEquals(x.pollFirst(), y.pollFirst()); + } + assertTrue(y.isEmpty()); + } + + /** + * subSet returns set with keys in requested range + */ + public void testSubSetContents() { + NavigableSet set = set5(); + SortedSet sm = set.subSet(two, four); + assertEquals(two, sm.first()); + assertEquals(three, sm.last()); + assertEquals(2, sm.size()); + assertFalse(sm.contains(one)); + assertTrue(sm.contains(two)); + assertTrue(sm.contains(three)); + assertFalse(sm.contains(four)); + assertFalse(sm.contains(five)); + Iterator i = sm.iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(two, k); + k = (Integer)(i.next()); + assertEquals(three, k); + assertFalse(i.hasNext()); + Iterator j = sm.iterator(); + j.next(); + j.remove(); + assertFalse(set.contains(two)); + assertEquals(4, set.size()); + assertEquals(1, sm.size()); + assertEquals(three, sm.first()); + assertEquals(three, sm.last()); + assertTrue(sm.remove(three)); + assertTrue(sm.isEmpty()); + assertEquals(3, set.size()); + } + + public void testSubSetContents2() { + NavigableSet set = set5(); + SortedSet sm = set.subSet(two, three); + assertEquals(1, sm.size()); + assertEquals(two, sm.first()); + assertEquals(two, sm.last()); + assertFalse(sm.contains(one)); + assertTrue(sm.contains(two)); + assertFalse(sm.contains(three)); + assertFalse(sm.contains(four)); + assertFalse(sm.contains(five)); + Iterator i = sm.iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(two, k); + assertFalse(i.hasNext()); + Iterator j = sm.iterator(); + j.next(); + j.remove(); + assertFalse(set.contains(two)); + assertEquals(4, set.size()); + assertEquals(0, sm.size()); + assertTrue(sm.isEmpty()); + assertFalse(sm.remove(three)); + assertEquals(4, set.size()); + } + + /** + * headSet returns set with keys in requested range + */ + public void testHeadSetContents() { + NavigableSet set = set5(); + SortedSet sm = set.headSet(four); + assertTrue(sm.contains(one)); + assertTrue(sm.contains(two)); + assertTrue(sm.contains(three)); + assertFalse(sm.contains(four)); + assertFalse(sm.contains(five)); + Iterator i = sm.iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(one, k); + k = (Integer)(i.next()); + assertEquals(two, k); + k = (Integer)(i.next()); + assertEquals(three, k); + assertFalse(i.hasNext()); + sm.clear(); + assertTrue(sm.isEmpty()); + assertEquals(2, set.size()); + assertEquals(four, set.first()); + } + + /** + * tailSet returns set with keys in requested range + */ + public void testTailSetContents() { + NavigableSet set = set5(); + SortedSet sm = set.tailSet(two); + assertFalse(sm.contains(one)); + assertTrue(sm.contains(two)); + assertTrue(sm.contains(three)); + assertTrue(sm.contains(four)); + assertTrue(sm.contains(five)); + Iterator i = sm.iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(two, k); + k = (Integer)(i.next()); + assertEquals(three, k); + k = (Integer)(i.next()); + assertEquals(four, k); + k = (Integer)(i.next()); + assertEquals(five, k); + assertFalse(i.hasNext()); + + SortedSet ssm = sm.tailSet(four); + assertEquals(four, ssm.first()); + assertEquals(five, ssm.last()); + assertTrue(ssm.remove(four)); + assertEquals(1, ssm.size()); + assertEquals(3, sm.size()); + assertEquals(4, set.size()); + } + + /** + * size changes when elements added and removed + */ + public void testDescendingSize() { + NavigableSet q = populatedSet(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(SIZE - i, q.size()); + q.pollFirst(); + } + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.size()); + q.add(new Integer(i)); + } + } + + /** + * add(null) throws NPE + */ + public void testDescendingAddNull() { + NavigableSet q = dset0(); + try { + q.add(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Add of comparable element succeeds + */ + public void testDescendingAdd() { + NavigableSet q = dset0(); + assertTrue(q.add(m6)); + } + + /** + * Add of duplicate element fails + */ + public void testDescendingAddDup() { + NavigableSet q = dset0(); + assertTrue(q.add(m6)); + assertFalse(q.add(m6)); + } + + /** + * Add of non-Comparable throws CCE + */ + public void testDescendingAddNonComparable() { + NavigableSet q = dset0(); + try { + q.add(new Object()); + q.add(new Object()); + shouldThrow(); + } catch (ClassCastException success) {} + } + + /** + * addAll(null) throws NPE + */ + public void testDescendingAddAll1() { + NavigableSet q = dset0(); + try { + q.addAll(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll of a collection with null elements throws NPE + */ + public void testDescendingAddAll2() { + NavigableSet q = dset0(); + Integer[] ints = new Integer[SIZE]; + try { + q.addAll(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll of a collection with any null elements throws NPE after + * possibly adding some elements + */ + public void testDescendingAddAll3() { + NavigableSet q = dset0(); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i + SIZE); + try { + q.addAll(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Set contains all elements of successful addAll + */ + public void testDescendingAddAll5() { + Integer[] empty = new Integer[0]; + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(SIZE - 1 - i); + NavigableSet q = dset0(); + assertFalse(q.addAll(Arrays.asList(empty))); + assertTrue(q.addAll(Arrays.asList(ints))); + for (int i = 0; i < SIZE; ++i) + assertEquals(new Integer(i), q.pollFirst()); + } + + /** + * poll succeeds unless empty + */ + public void testDescendingPoll() { + NavigableSet q = populatedSet(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.pollFirst()); + } + assertNull(q.pollFirst()); + } + + /** + * remove(x) removes x and returns true if present + */ + public void testDescendingRemoveElement() { + NavigableSet q = populatedSet(SIZE); + for (int i = 1; i < SIZE; i += 2) { + assertTrue(q.remove(new Integer(i))); + } + for (int i = 0; i < SIZE; i += 2 ) { + assertTrue(q.remove(new Integer(i))); + assertFalse(q.remove(new Integer(i + 1))); + } + assertTrue(q.isEmpty()); + } + + /** + * contains(x) reports true when elements added but not yet removed + */ + public void testDescendingContains() { + NavigableSet q = populatedSet(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.contains(new Integer(i))); + q.pollFirst(); + assertFalse(q.contains(new Integer(i))); + } + } + + /** + * clear removes all elements + */ + public void testDescendingClear() { + NavigableSet q = populatedSet(SIZE); + q.clear(); + assertTrue(q.isEmpty()); + assertEquals(0, q.size()); + q.add(new Integer(1)); + assertFalse(q.isEmpty()); + q.clear(); + assertTrue(q.isEmpty()); + } + + /** + * containsAll(c) is true when c contains a subset of elements + */ + public void testDescendingContainsAll() { + NavigableSet q = populatedSet(SIZE); + NavigableSet p = dset0(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.containsAll(p)); + assertFalse(p.containsAll(q)); + p.add(new Integer(i)); + } + assertTrue(p.containsAll(q)); + } + + /** + * retainAll(c) retains only those elements of c and reports true if changed + */ + public void testDescendingRetainAll() { + NavigableSet q = populatedSet(SIZE); + NavigableSet p = populatedSet(SIZE); + for (int i = 0; i < SIZE; ++i) { + boolean changed = q.retainAll(p); + if (i == 0) + assertFalse(changed); + else + assertTrue(changed); + + assertTrue(q.containsAll(p)); + assertEquals(SIZE - i, q.size()); + p.pollFirst(); + } + } + + /** + * removeAll(c) removes only those elements of c and reports true if changed + */ + public void testDescendingRemoveAll() { + for (int i = 1; i < SIZE; ++i) { + NavigableSet q = populatedSet(SIZE); + NavigableSet p = populatedSet(i); + assertTrue(q.removeAll(p)); + assertEquals(SIZE - i, q.size()); + for (int j = 0; j < i; ++j) { + Integer x = (Integer)(p.pollFirst()); + assertFalse(q.contains(x)); + } + } + } + + /** + * lower returns preceding element + */ + public void testDescendingLower() { + NavigableSet q = dset5(); + Object e1 = q.lower(m3); + assertEquals(m2, e1); + + Object e2 = q.lower(m6); + assertEquals(m5, e2); + + Object e3 = q.lower(m1); + assertNull(e3); + + Object e4 = q.lower(zero); + assertNull(e4); + } + + /** + * higher returns next element + */ + public void testDescendingHigher() { + NavigableSet q = dset5(); + Object e1 = q.higher(m3); + assertEquals(m4, e1); + + Object e2 = q.higher(zero); + assertEquals(m1, e2); + + Object e3 = q.higher(m5); + assertNull(e3); + + Object e4 = q.higher(m6); + assertNull(e4); + } + + /** + * floor returns preceding element + */ + public void testDescendingFloor() { + NavigableSet q = dset5(); + Object e1 = q.floor(m3); + assertEquals(m3, e1); + + Object e2 = q.floor(m6); + assertEquals(m5, e2); + + Object e3 = q.floor(m1); + assertEquals(m1, e3); + + Object e4 = q.floor(zero); + assertNull(e4); + } + + /** + * ceiling returns next element + */ + public void testDescendingCeiling() { + NavigableSet q = dset5(); + Object e1 = q.ceiling(m3); + assertEquals(m3, e1); + + Object e2 = q.ceiling(zero); + assertEquals(m1, e2); + + Object e3 = q.ceiling(m5); + assertEquals(m5, e3); + + Object e4 = q.ceiling(m6); + assertNull(e4); + } + + /** + * toArray contains all elements + */ + public void testDescendingToArray() { + NavigableSet q = populatedSet(SIZE); + Object[] o = q.toArray(); + Arrays.sort(o); + for (int i = 0; i < o.length; i++) + assertEquals(o[i], q.pollFirst()); + } + + /** + * toArray(a) contains all elements + */ + public void testDescendingToArray2() { + NavigableSet q = populatedSet(SIZE); + Integer[] ints = new Integer[SIZE]; + assertSame(ints, q.toArray(ints)); + Arrays.sort(ints); + for (int i = 0; i < ints.length; i++) + assertEquals(ints[i], q.pollFirst()); + } + + /** + * iterator iterates through all elements + */ + public void testDescendingIterator() { + NavigableSet q = populatedSet(SIZE); + int i = 0; + Iterator it = q.iterator(); + while (it.hasNext()) { + assertTrue(q.contains(it.next())); + ++i; + } + assertEquals(i, SIZE); + } + + /** + * iterator of empty set has no elements + */ + public void testDescendingEmptyIterator() { + NavigableSet q = dset0(); + int i = 0; + Iterator it = q.iterator(); + while (it.hasNext()) { + assertTrue(q.contains(it.next())); + ++i; + } + assertEquals(0, i); + } + + /** + * iterator.remove removes current element + */ + public void testDescendingIteratorRemove() { + final NavigableSet q = dset0(); + q.add(new Integer(2)); + q.add(new Integer(1)); + q.add(new Integer(3)); + + Iterator it = q.iterator(); + it.next(); + it.remove(); + + it = q.iterator(); + assertEquals(it.next(), new Integer(2)); + assertEquals(it.next(), new Integer(3)); + assertFalse(it.hasNext()); + } + + /** + * toString contains toStrings of elements + */ + public void testDescendingToString() { + NavigableSet q = populatedSet(SIZE); + String s = q.toString(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(s.contains(String.valueOf(i))); + } + } + + /** + * A deserialized serialized set has same elements + */ + public void testDescendingSerialization() throws Exception { + NavigableSet x = dset5(); + NavigableSet y = serialClone(x); + + assertNotSame(y, x); + assertEquals(x.size(), y.size()); + assertEquals(x, y); + assertEquals(y, x); + while (!x.isEmpty()) { + assertFalse(y.isEmpty()); + assertEquals(x.pollFirst(), y.pollFirst()); + } + assertTrue(y.isEmpty()); + } + + /** + * subSet returns set with keys in requested range + */ + public void testDescendingSubSetContents() { + NavigableSet set = dset5(); + SortedSet sm = set.subSet(m2, m4); + assertEquals(m2, sm.first()); + assertEquals(m3, sm.last()); + assertEquals(2, sm.size()); + assertFalse(sm.contains(m1)); + assertTrue(sm.contains(m2)); + assertTrue(sm.contains(m3)); + assertFalse(sm.contains(m4)); + assertFalse(sm.contains(m5)); + Iterator i = sm.iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(m2, k); + k = (Integer)(i.next()); + assertEquals(m3, k); + assertFalse(i.hasNext()); + Iterator j = sm.iterator(); + j.next(); + j.remove(); + assertFalse(set.contains(m2)); + assertEquals(4, set.size()); + assertEquals(1, sm.size()); + assertEquals(m3, sm.first()); + assertEquals(m3, sm.last()); + assertTrue(sm.remove(m3)); + assertTrue(sm.isEmpty()); + assertEquals(3, set.size()); + } + + public void testDescendingSubSetContents2() { + NavigableSet set = dset5(); + SortedSet sm = set.subSet(m2, m3); + assertEquals(1, sm.size()); + assertEquals(m2, sm.first()); + assertEquals(m2, sm.last()); + assertFalse(sm.contains(m1)); + assertTrue(sm.contains(m2)); + assertFalse(sm.contains(m3)); + assertFalse(sm.contains(m4)); + assertFalse(sm.contains(m5)); + Iterator i = sm.iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(m2, k); + assertFalse(i.hasNext()); + Iterator j = sm.iterator(); + j.next(); + j.remove(); + assertFalse(set.contains(m2)); + assertEquals(4, set.size()); + assertEquals(0, sm.size()); + assertTrue(sm.isEmpty()); + assertFalse(sm.remove(m3)); + assertEquals(4, set.size()); + } + + /** + * headSet returns set with keys in requested range + */ + public void testDescendingHeadSetContents() { + NavigableSet set = dset5(); + SortedSet sm = set.headSet(m4); + assertTrue(sm.contains(m1)); + assertTrue(sm.contains(m2)); + assertTrue(sm.contains(m3)); + assertFalse(sm.contains(m4)); + assertFalse(sm.contains(m5)); + Iterator i = sm.iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(m1, k); + k = (Integer)(i.next()); + assertEquals(m2, k); + k = (Integer)(i.next()); + assertEquals(m3, k); + assertFalse(i.hasNext()); + sm.clear(); + assertTrue(sm.isEmpty()); + assertEquals(2, set.size()); + assertEquals(m4, set.first()); + } + + /** + * tailSet returns set with keys in requested range + */ + public void testDescendingTailSetContents() { + NavigableSet set = dset5(); + SortedSet sm = set.tailSet(m2); + assertFalse(sm.contains(m1)); + assertTrue(sm.contains(m2)); + assertTrue(sm.contains(m3)); + assertTrue(sm.contains(m4)); + assertTrue(sm.contains(m5)); + Iterator i = sm.iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(m2, k); + k = (Integer)(i.next()); + assertEquals(m3, k); + k = (Integer)(i.next()); + assertEquals(m4, k); + k = (Integer)(i.next()); + assertEquals(m5, k); + assertFalse(i.hasNext()); + + SortedSet ssm = sm.tailSet(m4); + assertEquals(m4, ssm.first()); + assertEquals(m5, ssm.last()); + assertTrue(ssm.remove(m4)); + assertEquals(1, ssm.size()); + assertEquals(3, sm.size()); + assertEquals(4, set.size()); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/CopyOnWriteArrayListTest.java b/jdk/test/java/util/concurrent/tck/CopyOnWriteArrayListTest.java new file mode 100644 index 00000000000..75cfb1435fc --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/CopyOnWriteArrayListTest.java @@ -0,0 +1,783 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; +import java.util.concurrent.CopyOnWriteArrayList; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class CopyOnWriteArrayListTest extends JSR166TestCase { + + public static void main(String[] args) { + main(suite(), args); + } + + public static Test suite() { + return new TestSuite(CopyOnWriteArrayListTest.class); + } + + static CopyOnWriteArrayList populatedArray(int n) { + CopyOnWriteArrayList a = new CopyOnWriteArrayList(); + assertTrue(a.isEmpty()); + for (int i = 0; i < n; i++) + a.add(i); + assertFalse(a.isEmpty()); + assertEquals(n, a.size()); + return a; + } + + static CopyOnWriteArrayList populatedArray(Integer[] elements) { + CopyOnWriteArrayList a = new CopyOnWriteArrayList(); + assertTrue(a.isEmpty()); + for (int i = 0; i < elements.length; i++) + a.add(elements[i]); + assertFalse(a.isEmpty()); + assertEquals(elements.length, a.size()); + return a; + } + + /** + * a new list is empty + */ + public void testConstructor() { + CopyOnWriteArrayList a = new CopyOnWriteArrayList(); + assertTrue(a.isEmpty()); + } + + /** + * new list contains all elements of initializing array + */ + public void testConstructor2() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); + CopyOnWriteArrayList a = new CopyOnWriteArrayList(ints); + for (int i = 0; i < SIZE; ++i) + assertEquals(ints[i], a.get(i)); + } + + /** + * new list contains all elements of initializing collection + */ + public void testConstructor3() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); + CopyOnWriteArrayList a = new CopyOnWriteArrayList(Arrays.asList(ints)); + for (int i = 0; i < SIZE; ++i) + assertEquals(ints[i], a.get(i)); + } + + /** + * addAll adds each element from the given collection, including duplicates + */ + public void testAddAll() { + CopyOnWriteArrayList full = populatedArray(3); + assertTrue(full.addAll(Arrays.asList(three, four, five))); + assertEquals(6, full.size()); + assertTrue(full.addAll(Arrays.asList(three, four, five))); + assertEquals(9, full.size()); + } + + /** + * addAllAbsent adds each element from the given collection that did not + * already exist in the List + */ + public void testAddAllAbsent() { + CopyOnWriteArrayList full = populatedArray(3); + // "one" is duplicate and will not be added + assertEquals(2, full.addAllAbsent(Arrays.asList(three, four, one))); + assertEquals(5, full.size()); + assertEquals(0, full.addAllAbsent(Arrays.asList(three, four, one))); + assertEquals(5, full.size()); + } + + /** + * addIfAbsent will not add the element if it already exists in the list + */ + public void testAddIfAbsent() { + CopyOnWriteArrayList full = populatedArray(SIZE); + full.addIfAbsent(one); + assertEquals(SIZE, full.size()); + } + + /** + * addIfAbsent adds the element when it does not exist in the list + */ + public void testAddIfAbsent2() { + CopyOnWriteArrayList full = populatedArray(SIZE); + full.addIfAbsent(three); + assertTrue(full.contains(three)); + } + + /** + * clear removes all elements from the list + */ + public void testClear() { + CopyOnWriteArrayList full = populatedArray(SIZE); + full.clear(); + assertEquals(0, full.size()); + } + + /** + * Cloned list is equal + */ + public void testClone() { + CopyOnWriteArrayList l1 = populatedArray(SIZE); + CopyOnWriteArrayList l2 = (CopyOnWriteArrayList)(l1.clone()); + assertEquals(l1, l2); + l1.clear(); + assertFalse(l1.equals(l2)); + } + + /** + * contains is true for added elements + */ + public void testContains() { + CopyOnWriteArrayList full = populatedArray(3); + assertTrue(full.contains(one)); + assertFalse(full.contains(five)); + } + + /** + * adding at an index places it in the indicated index + */ + public void testAddIndex() { + CopyOnWriteArrayList full = populatedArray(3); + full.add(0, m1); + assertEquals(4, full.size()); + assertEquals(m1, full.get(0)); + assertEquals(zero, full.get(1)); + + full.add(2, m2); + assertEquals(5, full.size()); + assertEquals(m2, full.get(2)); + assertEquals(two, full.get(4)); + } + + /** + * lists with same elements are equal and have same hashCode + */ + public void testEquals() { + CopyOnWriteArrayList a = populatedArray(3); + CopyOnWriteArrayList b = populatedArray(3); + assertTrue(a.equals(b)); + assertTrue(b.equals(a)); + assertTrue(a.containsAll(b)); + assertTrue(b.containsAll(a)); + assertEquals(a.hashCode(), b.hashCode()); + a.add(m1); + assertFalse(a.equals(b)); + assertFalse(b.equals(a)); + assertTrue(a.containsAll(b)); + assertFalse(b.containsAll(a)); + b.add(m1); + assertTrue(a.equals(b)); + assertTrue(b.equals(a)); + assertTrue(a.containsAll(b)); + assertTrue(b.containsAll(a)); + assertEquals(a.hashCode(), b.hashCode()); + + assertFalse(a.equals(null)); + } + + /** + * containsAll returns true for collections with subset of elements + */ + public void testContainsAll() { + CopyOnWriteArrayList full = populatedArray(3); + assertTrue(full.containsAll(Arrays.asList())); + assertTrue(full.containsAll(Arrays.asList(one))); + assertTrue(full.containsAll(Arrays.asList(one, two))); + assertFalse(full.containsAll(Arrays.asList(one, two, six))); + assertFalse(full.containsAll(Arrays.asList(six))); + + try { + full.containsAll(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * get returns the value at the given index + */ + public void testGet() { + CopyOnWriteArrayList full = populatedArray(3); + assertEquals(0, full.get(0)); + } + + /** + * indexOf gives the index for the given object + */ + public void testIndexOf() { + CopyOnWriteArrayList full = populatedArray(3); + assertEquals(1, full.indexOf(one)); + assertEquals(-1, full.indexOf("puppies")); + } + + /** + * indexOf gives the index based on the given index + * at which to start searching + */ + public void testIndexOf2() { + CopyOnWriteArrayList full = populatedArray(3); + assertEquals(1, full.indexOf(one, 0)); + assertEquals(-1, full.indexOf(one, 2)); + } + + /** + * isEmpty returns true when empty, else false + */ + public void testIsEmpty() { + CopyOnWriteArrayList empty = new CopyOnWriteArrayList(); + CopyOnWriteArrayList full = populatedArray(SIZE); + assertTrue(empty.isEmpty()); + assertFalse(full.isEmpty()); + } + + /** + * iterator() returns an iterator containing the elements of the + * list in insertion order + */ + public void testIterator() { + Collection empty = new CopyOnWriteArrayList(); + assertFalse(empty.iterator().hasNext()); + try { + empty.iterator().next(); + shouldThrow(); + } catch (NoSuchElementException success) {} + + Integer[] elements = new Integer[SIZE]; + for (int i = 0; i < SIZE; i++) + elements[i] = i; + Collections.shuffle(Arrays.asList(elements)); + Collection full = populatedArray(elements); + + Iterator it = full.iterator(); + for (int j = 0; j < SIZE; j++) { + assertTrue(it.hasNext()); + assertEquals(elements[j], it.next()); + } + assertIteratorExhausted(it); + } + + /** + * iterator of empty collection has no elements + */ + public void testEmptyIterator() { + Collection c = new CopyOnWriteArrayList(); + assertIteratorExhausted(c.iterator()); + } + + /** + * iterator.remove throws UnsupportedOperationException + */ + public void testIteratorRemove() { + CopyOnWriteArrayList full = populatedArray(SIZE); + Iterator it = full.iterator(); + it.next(); + try { + it.remove(); + shouldThrow(); + } catch (UnsupportedOperationException success) {} + } + + /** + * toString contains toString of elements + */ + public void testToString() { + assertEquals("[]", new CopyOnWriteArrayList().toString()); + CopyOnWriteArrayList full = populatedArray(3); + String s = full.toString(); + for (int i = 0; i < 3; ++i) + assertTrue(s.contains(String.valueOf(i))); + assertEquals(new ArrayList(full).toString(), + full.toString()); + } + + /** + * lastIndexOf returns the index for the given object + */ + public void testLastIndexOf1() { + CopyOnWriteArrayList full = populatedArray(3); + full.add(one); + full.add(three); + assertEquals(3, full.lastIndexOf(one)); + assertEquals(-1, full.lastIndexOf(six)); + } + + /** + * lastIndexOf returns the index from the given starting point + */ + public void testLastIndexOf2() { + CopyOnWriteArrayList full = populatedArray(3); + full.add(one); + full.add(three); + assertEquals(3, full.lastIndexOf(one, 4)); + assertEquals(-1, full.lastIndexOf(three, 3)); + } + + /** + * listIterator traverses all elements + */ + public void testListIterator1() { + CopyOnWriteArrayList full = populatedArray(SIZE); + ListIterator i = full.listIterator(); + int j; + for (j = 0; i.hasNext(); j++) + assertEquals(j, i.next()); + assertEquals(SIZE, j); + } + + /** + * listIterator only returns those elements after the given index + */ + public void testListIterator2() { + CopyOnWriteArrayList full = populatedArray(3); + ListIterator i = full.listIterator(1); + int j; + for (j = 0; i.hasNext(); j++) + assertEquals(j + 1, i.next()); + assertEquals(2, j); + } + + /** + * remove(int) removes and returns the object at the given index + */ + public void testRemove_int() { + int SIZE = 3; + for (int i = 0; i < SIZE; i++) { + CopyOnWriteArrayList full = populatedArray(SIZE); + assertEquals(i, full.remove(i)); + assertEquals(SIZE - 1, full.size()); + assertFalse(full.contains(new Integer(i))); + } + } + + /** + * remove(Object) removes the object if found and returns true + */ + public void testRemove_Object() { + int SIZE = 3; + for (int i = 0; i < SIZE; i++) { + CopyOnWriteArrayList full = populatedArray(SIZE); + assertFalse(full.remove(new Integer(-42))); + assertTrue(full.remove(new Integer(i))); + assertEquals(SIZE - 1, full.size()); + assertFalse(full.contains(new Integer(i))); + } + CopyOnWriteArrayList x = new CopyOnWriteArrayList(Arrays.asList(4, 5, 6)); + assertTrue(x.remove(new Integer(6))); + assertEquals(x, Arrays.asList(4, 5)); + assertTrue(x.remove(new Integer(4))); + assertEquals(x, Arrays.asList(5)); + assertTrue(x.remove(new Integer(5))); + assertEquals(x, Arrays.asList()); + assertFalse(x.remove(new Integer(5))); + } + + /** + * removeAll removes all elements from the given collection + */ + public void testRemoveAll() { + CopyOnWriteArrayList full = populatedArray(3); + assertTrue(full.removeAll(Arrays.asList(one, two))); + assertEquals(1, full.size()); + assertFalse(full.removeAll(Arrays.asList(one, two))); + assertEquals(1, full.size()); + } + + /** + * set changes the element at the given index + */ + public void testSet() { + CopyOnWriteArrayList full = populatedArray(3); + assertEquals(2, full.set(2, four)); + assertEquals(4, full.get(2)); + } + + /** + * size returns the number of elements + */ + public void testSize() { + CopyOnWriteArrayList empty = new CopyOnWriteArrayList(); + CopyOnWriteArrayList full = populatedArray(SIZE); + assertEquals(SIZE, full.size()); + assertEquals(0, empty.size()); + } + + /** + * toArray() returns an Object array containing all elements from + * the list in insertion order + */ + public void testToArray() { + Object[] a = new CopyOnWriteArrayList().toArray(); + assertTrue(Arrays.equals(new Object[0], a)); + assertSame(Object[].class, a.getClass()); + + Integer[] elements = new Integer[SIZE]; + for (int i = 0; i < SIZE; i++) + elements[i] = i; + Collections.shuffle(Arrays.asList(elements)); + Collection full = populatedArray(elements); + + assertTrue(Arrays.equals(elements, full.toArray())); + assertSame(Object[].class, full.toArray().getClass()); + } + + /** + * toArray(Integer array) returns an Integer array containing all + * elements from the list in insertion order + */ + public void testToArray2() { + Collection empty = new CopyOnWriteArrayList(); + Integer[] a; + + a = new Integer[0]; + assertSame(a, empty.toArray(a)); + + a = new Integer[SIZE / 2]; + Arrays.fill(a, 42); + assertSame(a, empty.toArray(a)); + assertNull(a[0]); + for (int i = 1; i < a.length; i++) + assertEquals(42, (int) a[i]); + + Integer[] elements = new Integer[SIZE]; + for (int i = 0; i < SIZE; i++) + elements[i] = i; + Collections.shuffle(Arrays.asList(elements)); + Collection full = populatedArray(elements); + + Arrays.fill(a, 42); + assertTrue(Arrays.equals(elements, full.toArray(a))); + for (int i = 0; i < a.length; i++) + assertEquals(42, (int) a[i]); + assertSame(Integer[].class, full.toArray(a).getClass()); + + a = new Integer[SIZE]; + Arrays.fill(a, 42); + assertSame(a, full.toArray(a)); + assertTrue(Arrays.equals(elements, a)); + + a = new Integer[2 * SIZE]; + Arrays.fill(a, 42); + assertSame(a, full.toArray(a)); + assertTrue(Arrays.equals(elements, Arrays.copyOf(a, SIZE))); + assertNull(a[SIZE]); + for (int i = SIZE + 1; i < a.length; i++) + assertEquals(42, (int) a[i]); + } + + /** + * sublists contains elements at indexes offset from their base + */ + public void testSubList() { + CopyOnWriteArrayList a = populatedArray(10); + assertTrue(a.subList(1,1).isEmpty()); + for (int j = 0; j < 9; ++j) { + for (int i = j ; i < 10; ++i) { + List b = a.subList(j,i); + for (int k = j; k < i; ++k) { + assertEquals(new Integer(k), b.get(k-j)); + } + } + } + + List s = a.subList(2, 5); + assertEquals(3, s.size()); + s.set(2, m1); + assertEquals(a.get(4), m1); + s.clear(); + assertEquals(7, a.size()); + } + + // Exception tests + + /** + * toArray throws an ArrayStoreException when the given array + * can not store the objects inside the list + */ + public void testToArray_ArrayStoreException() { + CopyOnWriteArrayList c = new CopyOnWriteArrayList(); + c.add("zfasdfsdf"); + c.add("asdadasd"); + try { + c.toArray(new Long[5]); + shouldThrow(); + } catch (ArrayStoreException success) {} + } + + /** + * get throws an IndexOutOfBoundsException on a negative index + */ + public void testGet1_IndexOutOfBoundsException() { + CopyOnWriteArrayList c = populatedArray(5); + List[] lists = { c, c.subList(1, c.size() - 1) }; + for (List list : lists) { + try { + list.get(-1); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + } + } + + /** + * get throws an IndexOutOfBoundsException on a too high index + */ + public void testGet2_IndexOutOfBoundsException() { + CopyOnWriteArrayList c = populatedArray(5); + List[] lists = { c, c.subList(1, c.size() - 1) }; + for (List list : lists) { + try { + list.get(list.size()); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + } + } + + /** + * set throws an IndexOutOfBoundsException on a negative index + */ + public void testSet1_IndexOutOfBoundsException() { + CopyOnWriteArrayList c = populatedArray(5); + List[] lists = { c, c.subList(1, c.size() - 1) }; + for (List list : lists) { + try { + list.set(-1, "qwerty"); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + } + } + + /** + * set throws an IndexOutOfBoundsException on a too high index + */ + public void testSet2() { + CopyOnWriteArrayList c = populatedArray(5); + List[] lists = { c, c.subList(1, c.size() - 1) }; + for (List list : lists) { + try { + list.set(list.size(), "qwerty"); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + } + } + + /** + * add throws an IndexOutOfBoundsException on a negative index + */ + public void testAdd1_IndexOutOfBoundsException() { + CopyOnWriteArrayList c = populatedArray(5); + List[] lists = { c, c.subList(1, c.size() - 1) }; + for (List list : lists) { + try { + list.add(-1, "qwerty"); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + } + } + + /** + * add throws an IndexOutOfBoundsException on a too high index + */ + public void testAdd2_IndexOutOfBoundsException() { + CopyOnWriteArrayList c = populatedArray(5); + List[] lists = { c, c.subList(1, c.size() - 1) }; + for (List list : lists) { + try { + list.add(list.size() + 1, "qwerty"); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + } + } + + /** + * remove throws an IndexOutOfBoundsException on a negative index + */ + public void testRemove1_IndexOutOfBounds() { + CopyOnWriteArrayList c = populatedArray(5); + List[] lists = { c, c.subList(1, c.size() - 1) }; + for (List list : lists) { + try { + list.remove(-1); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + } + } + + /** + * remove throws an IndexOutOfBoundsException on a too high index + */ + public void testRemove2_IndexOutOfBounds() { + CopyOnWriteArrayList c = populatedArray(5); + List[] lists = { c, c.subList(1, c.size() - 1) }; + for (List list : lists) { + try { + list.remove(list.size()); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + } + } + + /** + * addAll throws an IndexOutOfBoundsException on a negative index + */ + public void testAddAll1_IndexOutOfBoundsException() { + CopyOnWriteArrayList c = populatedArray(5); + List[] lists = { c, c.subList(1, c.size() - 1) }; + for (List list : lists) { + try { + list.addAll(-1, new LinkedList()); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + } + } + + /** + * addAll throws an IndexOutOfBoundsException on a too high index + */ + public void testAddAll2_IndexOutOfBoundsException() { + CopyOnWriteArrayList c = populatedArray(5); + List[] lists = { c, c.subList(1, c.size() - 1) }; + for (List list : lists) { + try { + list.addAll(list.size() + 1, new LinkedList()); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + } + } + + /** + * listIterator throws an IndexOutOfBoundsException on a negative index + */ + public void testListIterator1_IndexOutOfBoundsException() { + CopyOnWriteArrayList c = populatedArray(5); + List[] lists = { c, c.subList(1, c.size() - 1) }; + for (List list : lists) { + try { + list.listIterator(-1); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + } + } + + /** + * listIterator throws an IndexOutOfBoundsException on a too high index + */ + public void testListIterator2_IndexOutOfBoundsException() { + CopyOnWriteArrayList c = populatedArray(5); + List[] lists = { c, c.subList(1, c.size() - 1) }; + for (List list : lists) { + try { + list.listIterator(list.size() + 1); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + } + } + + /** + * subList throws an IndexOutOfBoundsException on a negative index + */ + public void testSubList1_IndexOutOfBoundsException() { + CopyOnWriteArrayList c = populatedArray(5); + List[] lists = { c, c.subList(1, c.size() - 1) }; + for (List list : lists) { + try { + list.subList(-1, list.size()); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + } + } + + /** + * subList throws an IndexOutOfBoundsException on a too high index + */ + public void testSubList2_IndexOutOfBoundsException() { + CopyOnWriteArrayList c = populatedArray(5); + List[] lists = { c, c.subList(1, c.size() - 1) }; + for (List list : lists) { + try { + list.subList(0, list.size() + 1); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + } + } + + /** + * subList throws IndexOutOfBoundsException when the second index + * is lower then the first + */ + public void testSubList3_IndexOutOfBoundsException() { + CopyOnWriteArrayList c = populatedArray(5); + List[] lists = { c, c.subList(1, c.size() - 1) }; + for (List list : lists) { + try { + list.subList(list.size() - 1, 1); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + } + } + + /** + * a deserialized serialized list is equal + */ + public void testSerialization() throws Exception { + List x = populatedArray(SIZE); + List y = serialClone(x); + + assertNotSame(x, y); + assertEquals(x.size(), y.size()); + assertEquals(x.toString(), y.toString()); + assertTrue(Arrays.equals(x.toArray(), y.toArray())); + assertEquals(x, y); + assertEquals(y, x); + while (!x.isEmpty()) { + assertFalse(y.isEmpty()); + assertEquals(x.remove(0), y.remove(0)); + } + assertTrue(y.isEmpty()); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/CopyOnWriteArraySetTest.java b/jdk/test/java/util/concurrent/tck/CopyOnWriteArraySetTest.java new file mode 100644 index 00000000000..bd9a7e3da43 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/CopyOnWriteArraySetTest.java @@ -0,0 +1,432 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class CopyOnWriteArraySetTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(CopyOnWriteArraySetTest.class); + } + + static CopyOnWriteArraySet populatedSet(int n) { + CopyOnWriteArraySet a = new CopyOnWriteArraySet(); + assertTrue(a.isEmpty()); + for (int i = 0; i < n; i++) + a.add(i); + assertEquals(n == 0, a.isEmpty()); + assertEquals(n, a.size()); + return a; + } + + static CopyOnWriteArraySet populatedSet(Integer[] elements) { + CopyOnWriteArraySet a = new CopyOnWriteArraySet(); + assertTrue(a.isEmpty()); + for (int i = 0; i < elements.length; i++) + a.add(elements[i]); + assertFalse(a.isEmpty()); + assertEquals(elements.length, a.size()); + return a; + } + + /** + * Default-constructed set is empty + */ + public void testConstructor() { + CopyOnWriteArraySet a = new CopyOnWriteArraySet(); + assertTrue(a.isEmpty()); + } + + /** + * Collection-constructed set holds all of its elements + */ + public void testConstructor3() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); + CopyOnWriteArraySet a = new CopyOnWriteArraySet(Arrays.asList(ints)); + for (int i = 0; i < SIZE; ++i) + assertTrue(a.contains(ints[i])); + } + + /** + * addAll adds each non-duplicate element from the given collection + */ + public void testAddAll() { + Set full = populatedSet(3); + assertTrue(full.addAll(Arrays.asList(three, four, five))); + assertEquals(6, full.size()); + assertFalse(full.addAll(Arrays.asList(three, four, five))); + assertEquals(6, full.size()); + } + + /** + * addAll adds each non-duplicate element from the given collection + */ + public void testAddAll2() { + Set full = populatedSet(3); + // "one" is duplicate and will not be added + assertTrue(full.addAll(Arrays.asList(three, four, one))); + assertEquals(5, full.size()); + assertFalse(full.addAll(Arrays.asList(three, four, one))); + assertEquals(5, full.size()); + } + + /** + * add will not add the element if it already exists in the set + */ + public void testAdd2() { + Set full = populatedSet(3); + full.add(one); + assertEquals(3, full.size()); + } + + /** + * add adds the element when it does not exist in the set + */ + public void testAdd3() { + Set full = populatedSet(3); + full.add(three); + assertTrue(full.contains(three)); + } + + /** + * clear removes all elements from the set + */ + public void testClear() { + Collection full = populatedSet(3); + full.clear(); + assertEquals(0, full.size()); + assertTrue(full.isEmpty()); + } + + /** + * contains returns true for added elements + */ + public void testContains() { + Collection full = populatedSet(3); + assertTrue(full.contains(one)); + assertFalse(full.contains(five)); + } + + /** + * Sets with equal elements are equal + */ + public void testEquals() { + CopyOnWriteArraySet a = populatedSet(3); + CopyOnWriteArraySet b = populatedSet(3); + assertTrue(a.equals(b)); + assertTrue(b.equals(a)); + assertTrue(a.containsAll(b)); + assertTrue(b.containsAll(a)); + assertEquals(a.hashCode(), b.hashCode()); + assertEquals(a.size(), b.size()); + + a.add(m1); + assertFalse(a.equals(b)); + assertFalse(b.equals(a)); + assertTrue(a.containsAll(b)); + assertFalse(b.containsAll(a)); + b.add(m1); + assertTrue(a.equals(b)); + assertTrue(b.equals(a)); + assertTrue(a.containsAll(b)); + assertTrue(b.containsAll(a)); + assertEquals(a.hashCode(), b.hashCode()); + + Object x = a.iterator().next(); + a.remove(x); + assertFalse(a.equals(b)); + assertFalse(b.equals(a)); + assertFalse(a.containsAll(b)); + assertTrue(b.containsAll(a)); + a.add(x); + assertTrue(a.equals(b)); + assertTrue(b.equals(a)); + assertTrue(a.containsAll(b)); + assertTrue(b.containsAll(a)); + assertEquals(a.hashCode(), b.hashCode()); + assertEquals(a.size(), b.size()); + + CopyOnWriteArraySet empty1 = new CopyOnWriteArraySet(Arrays.asList()); + CopyOnWriteArraySet empty2 = new CopyOnWriteArraySet(Arrays.asList()); + assertTrue(empty1.equals(empty1)); + assertTrue(empty1.equals(empty2)); + + assertFalse(empty1.equals(a)); + assertFalse(a.equals(empty1)); + + assertFalse(a.equals(null)); + } + + /** + * containsAll returns true for collections with subset of elements + */ + public void testContainsAll() { + Collection full = populatedSet(3); + assertTrue(full.containsAll(full)); + assertTrue(full.containsAll(Arrays.asList())); + assertTrue(full.containsAll(Arrays.asList(one))); + assertTrue(full.containsAll(Arrays.asList(one, two))); + assertFalse(full.containsAll(Arrays.asList(one, two, six))); + assertFalse(full.containsAll(Arrays.asList(six))); + + CopyOnWriteArraySet empty1 = new CopyOnWriteArraySet(Arrays.asList()); + CopyOnWriteArraySet empty2 = new CopyOnWriteArraySet(Arrays.asList()); + assertTrue(empty1.containsAll(empty2)); + assertTrue(empty1.containsAll(empty1)); + assertFalse(empty1.containsAll(full)); + assertTrue(full.containsAll(empty1)); + + try { + full.containsAll(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * isEmpty is true when empty, else false + */ + public void testIsEmpty() { + assertTrue(populatedSet(0).isEmpty()); + assertFalse(populatedSet(3).isEmpty()); + } + + /** + * iterator() returns an iterator containing the elements of the + * set in insertion order + */ + public void testIterator() { + Collection empty = new CopyOnWriteArraySet(); + assertFalse(empty.iterator().hasNext()); + try { + empty.iterator().next(); + shouldThrow(); + } catch (NoSuchElementException success) {} + + Integer[] elements = new Integer[SIZE]; + for (int i = 0; i < SIZE; i++) + elements[i] = i; + Collections.shuffle(Arrays.asList(elements)); + Collection full = populatedSet(elements); + + Iterator it = full.iterator(); + for (int j = 0; j < SIZE; j++) { + assertTrue(it.hasNext()); + assertEquals(elements[j], it.next()); + } + assertIteratorExhausted(it); + } + + /** + * iterator of empty collection has no elements + */ + public void testEmptyIterator() { + assertIteratorExhausted(new CopyOnWriteArraySet().iterator()); + } + + /** + * iterator remove is unsupported + */ + public void testIteratorRemove() { + Collection full = populatedSet(3); + Iterator it = full.iterator(); + it.next(); + try { + it.remove(); + shouldThrow(); + } catch (UnsupportedOperationException success) {} + } + + /** + * toString holds toString of elements + */ + public void testToString() { + assertEquals("[]", new CopyOnWriteArraySet().toString()); + Collection full = populatedSet(3); + String s = full.toString(); + for (int i = 0; i < 3; ++i) + assertTrue(s.contains(String.valueOf(i))); + assertEquals(new ArrayList(full).toString(), + full.toString()); + } + + /** + * removeAll removes all elements from the given collection + */ + public void testRemoveAll() { + Set full = populatedSet(3); + assertTrue(full.removeAll(Arrays.asList(one, two))); + assertEquals(1, full.size()); + assertFalse(full.removeAll(Arrays.asList(one, two))); + assertEquals(1, full.size()); + } + + /** + * remove removes an element + */ + public void testRemove() { + Collection full = populatedSet(3); + full.remove(one); + assertFalse(full.contains(one)); + assertEquals(2, full.size()); + } + + /** + * size returns the number of elements + */ + public void testSize() { + Collection empty = new CopyOnWriteArraySet(); + Collection full = populatedSet(3); + assertEquals(3, full.size()); + assertEquals(0, empty.size()); + } + + /** + * toArray() returns an Object array containing all elements from + * the set in insertion order + */ + public void testToArray() { + Object[] a = new CopyOnWriteArraySet().toArray(); + assertTrue(Arrays.equals(new Object[0], a)); + assertSame(Object[].class, a.getClass()); + + Integer[] elements = new Integer[SIZE]; + for (int i = 0; i < SIZE; i++) + elements[i] = i; + Collections.shuffle(Arrays.asList(elements)); + Collection full = populatedSet(elements); + + assertTrue(Arrays.equals(elements, full.toArray())); + assertSame(Object[].class, full.toArray().getClass()); + } + + /** + * toArray(Integer array) returns an Integer array containing all + * elements from the set in insertion order + */ + public void testToArray2() { + Collection empty = new CopyOnWriteArraySet(); + Integer[] a; + + a = new Integer[0]; + assertSame(a, empty.toArray(a)); + + a = new Integer[SIZE / 2]; + Arrays.fill(a, 42); + assertSame(a, empty.toArray(a)); + assertNull(a[0]); + for (int i = 1; i < a.length; i++) + assertEquals(42, (int) a[i]); + + Integer[] elements = new Integer[SIZE]; + for (int i = 0; i < SIZE; i++) + elements[i] = i; + Collections.shuffle(Arrays.asList(elements)); + Collection full = populatedSet(elements); + + Arrays.fill(a, 42); + assertTrue(Arrays.equals(elements, full.toArray(a))); + for (int i = 0; i < a.length; i++) + assertEquals(42, (int) a[i]); + assertSame(Integer[].class, full.toArray(a).getClass()); + + a = new Integer[SIZE]; + Arrays.fill(a, 42); + assertSame(a, full.toArray(a)); + assertTrue(Arrays.equals(elements, a)); + + a = new Integer[2 * SIZE]; + Arrays.fill(a, 42); + assertSame(a, full.toArray(a)); + assertTrue(Arrays.equals(elements, Arrays.copyOf(a, SIZE))); + assertNull(a[SIZE]); + for (int i = SIZE + 1; i < a.length; i++) + assertEquals(42, (int) a[i]); + } + + /** + * toArray throws an ArrayStoreException when the given array can + * not store the objects inside the set + */ + public void testToArray_ArrayStoreException() { + CopyOnWriteArraySet c = new CopyOnWriteArraySet(); + c.add("zfasdfsdf"); + c.add("asdadasd"); + try { + c.toArray(new Long[5]); + shouldThrow(); + } catch (ArrayStoreException success) {} + } + + /** + * A deserialized serialized set is equal + */ + public void testSerialization() throws Exception { + Set x = populatedSet(SIZE); + Set y = serialClone(x); + + assertNotSame(y, x); + assertEquals(x.size(), y.size()); + assertEquals(x.toString(), y.toString()); + assertTrue(Arrays.equals(x.toArray(), y.toArray())); + assertEquals(x, y); + assertEquals(y, x); + } + + /** + * addAll is idempotent + */ + public void testAddAll_idempotent() throws Exception { + Set x = populatedSet(SIZE); + Set y = new CopyOnWriteArraySet(x); + y.addAll(x); + assertEquals(x, y); + assertEquals(y, x); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/CountDownLatchTest.java b/jdk/test/java/util/concurrent/tck/CountDownLatchTest.java new file mode 100644 index 00000000000..e286d00cdf9 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/CountDownLatchTest.java @@ -0,0 +1,223 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.concurrent.CountDownLatch; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class CountDownLatchTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(CountDownLatchTest.class); + } + + /** + * negative constructor argument throws IAE + */ + public void testConstructor() { + try { + new CountDownLatch(-1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * getCount returns initial count and decreases after countDown + */ + public void testGetCount() { + final CountDownLatch l = new CountDownLatch(2); + assertEquals(2, l.getCount()); + l.countDown(); + assertEquals(1, l.getCount()); + } + + /** + * countDown decrements count when positive and has no effect when zero + */ + public void testCountDown() { + final CountDownLatch l = new CountDownLatch(1); + assertEquals(1, l.getCount()); + l.countDown(); + assertEquals(0, l.getCount()); + l.countDown(); + assertEquals(0, l.getCount()); + } + + /** + * await returns after countDown to zero, but not before + */ + public void testAwait() { + final CountDownLatch l = new CountDownLatch(2); + final CountDownLatch pleaseCountDown = new CountDownLatch(1); + + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertEquals(2, l.getCount()); + pleaseCountDown.countDown(); + l.await(); + assertEquals(0, l.getCount()); + }}); + + await(pleaseCountDown); + assertEquals(2, l.getCount()); + l.countDown(); + assertEquals(1, l.getCount()); + assertThreadStaysAlive(t); + l.countDown(); + assertEquals(0, l.getCount()); + awaitTermination(t); + } + + /** + * timed await returns after countDown to zero + */ + public void testTimedAwait() { + final CountDownLatch l = new CountDownLatch(2); + final CountDownLatch pleaseCountDown = new CountDownLatch(1); + + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertEquals(2, l.getCount()); + pleaseCountDown.countDown(); + assertTrue(l.await(LONG_DELAY_MS, MILLISECONDS)); + assertEquals(0, l.getCount()); + }}); + + await(pleaseCountDown); + assertEquals(2, l.getCount()); + l.countDown(); + assertEquals(1, l.getCount()); + assertThreadStaysAlive(t); + l.countDown(); + assertEquals(0, l.getCount()); + awaitTermination(t); + } + + /** + * await throws IE if interrupted before counted down + */ + public void testAwait_Interruptible() { + final CountDownLatch l = new CountDownLatch(1); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + Thread.currentThread().interrupt(); + try { + l.await(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + l.await(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + assertEquals(1, l.getCount()); + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * timed await throws IE if interrupted before counted down + */ + public void testTimedAwait_Interruptible() { + final CountDownLatch l = new CountDownLatch(1); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + Thread.currentThread().interrupt(); + try { + l.await(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + l.await(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + assertEquals(1, l.getCount()); + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * timed await times out if not counted down before timeout + */ + public void testAwaitTimeout() throws InterruptedException { + final CountDownLatch l = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertEquals(1, l.getCount()); + assertFalse(l.await(timeoutMillis(), MILLISECONDS)); + assertEquals(1, l.getCount()); + }}); + + awaitTermination(t); + assertEquals(1, l.getCount()); + } + + /** + * toString indicates current count + */ + public void testToString() { + CountDownLatch s = new CountDownLatch(2); + assertTrue(s.toString().contains("Count = 2")); + s.countDown(); + assertTrue(s.toString().contains("Count = 1")); + s.countDown(); + assertTrue(s.toString().contains("Count = 0")); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/CountedCompleterTest.java b/jdk/test/java/util/concurrent/tck/CountedCompleterTest.java new file mode 100644 index 00000000000..8be66850920 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/CountedCompleterTest.java @@ -0,0 +1,1872 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; + +import java.util.HashSet; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CountedCompleter; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinTask; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class CountedCompleterTest extends JSR166TestCase { + + public static void main(String[] args) { + main(suite(), args); + } + + public static Test suite() { + return new TestSuite(CountedCompleterTest.class); + } + + // Runs with "mainPool" use > 1 thread. singletonPool tests use 1 + static final int mainPoolSize = + Math.max(2, Runtime.getRuntime().availableProcessors()); + + private static ForkJoinPool mainPool() { + return new ForkJoinPool(mainPoolSize); + } + + private static ForkJoinPool singletonPool() { + return new ForkJoinPool(1); + } + + private static ForkJoinPool asyncSingletonPool() { + return new ForkJoinPool(1, + ForkJoinPool.defaultForkJoinWorkerThreadFactory, + null, true); + } + + private void testInvokeOnPool(ForkJoinPool pool, ForkJoinTask a) { + try (PoolCleaner cleaner = cleaner(pool)) { + assertFalse(a.isDone()); + assertFalse(a.isCompletedNormally()); + assertFalse(a.isCompletedAbnormally()); + assertFalse(a.isCancelled()); + assertNull(a.getException()); + assertNull(a.getRawResult()); + + assertNull(pool.invoke(a)); + + assertTrue(a.isDone()); + assertTrue(a.isCompletedNormally()); + assertFalse(a.isCompletedAbnormally()); + assertFalse(a.isCancelled()); + assertNull(a.getException()); + assertNull(a.getRawResult()); + } + } + + void checkNotDone(CountedCompleter a) { + assertFalse(a.isDone()); + assertFalse(a.isCompletedNormally()); + assertFalse(a.isCompletedAbnormally()); + assertFalse(a.isCancelled()); + assertNull(a.getException()); + assertNull(a.getRawResult()); + + try { + a.get(0L, SECONDS); + shouldThrow(); + } catch (TimeoutException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + void checkCompletedNormally(CountedCompleter a) { + assertTrue(a.isDone()); + assertFalse(a.isCancelled()); + assertTrue(a.isCompletedNormally()); + assertFalse(a.isCompletedAbnormally()); + assertNull(a.getException()); + assertNull(a.getRawResult()); + + { + Thread.currentThread().interrupt(); + long startTime = System.nanoTime(); + assertNull(a.join()); + assertTrue(millisElapsedSince(startTime) < SMALL_DELAY_MS); + Thread.interrupted(); + } + + { + Thread.currentThread().interrupt(); + long startTime = System.nanoTime(); + a.quietlyJoin(); // should be no-op + assertTrue(millisElapsedSince(startTime) < SMALL_DELAY_MS); + Thread.interrupted(); + } + + assertFalse(a.cancel(false)); + assertFalse(a.cancel(true)); + try { + assertNull(a.get()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + try { + assertNull(a.get(5L, SECONDS)); + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + void checkCancelled(CountedCompleter a) { + assertTrue(a.isDone()); + assertTrue(a.isCancelled()); + assertFalse(a.isCompletedNormally()); + assertTrue(a.isCompletedAbnormally()); + assertTrue(a.getException() instanceof CancellationException); + assertNull(a.getRawResult()); + assertTrue(a.cancel(false)); + assertTrue(a.cancel(true)); + + try { + Thread.currentThread().interrupt(); + a.join(); + shouldThrow(); + } catch (CancellationException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + Thread.interrupted(); + + { + long startTime = System.nanoTime(); + a.quietlyJoin(); // should be no-op + assertTrue(millisElapsedSince(startTime) < SMALL_DELAY_MS); + } + + try { + a.get(); + shouldThrow(); + } catch (CancellationException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + + try { + a.get(5L, SECONDS); + shouldThrow(); + } catch (CancellationException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + void checkCompletedAbnormally(CountedCompleter a, Throwable t) { + assertTrue(a.isDone()); + assertFalse(a.isCancelled()); + assertFalse(a.isCompletedNormally()); + assertTrue(a.isCompletedAbnormally()); + assertSame(t.getClass(), a.getException().getClass()); + assertNull(a.getRawResult()); + assertFalse(a.cancel(false)); + assertFalse(a.cancel(true)); + + try { + Thread.currentThread().interrupt(); + a.join(); + shouldThrow(); + } catch (Throwable expected) { + assertSame(t.getClass(), expected.getClass()); + } + Thread.interrupted(); + + { + long startTime = System.nanoTime(); + a.quietlyJoin(); // should be no-op + assertTrue(millisElapsedSince(startTime) < SMALL_DELAY_MS); + } + + try { + a.get(); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(t.getClass(), success.getCause().getClass()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + + try { + a.get(5L, SECONDS); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(t.getClass(), success.getCause().getClass()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + + try { + a.invoke(); + shouldThrow(); + } catch (Throwable success) { + assertSame(t, success); + } + } + + public static final class FJException extends RuntimeException { + FJException() { super(); } + } + + abstract class CheckedCC extends CountedCompleter { + final AtomicInteger computeN = new AtomicInteger(0); + final AtomicInteger onCompletionN = new AtomicInteger(0); + final AtomicInteger onExceptionalCompletionN = new AtomicInteger(0); + final AtomicInteger setRawResultN = new AtomicInteger(0); + final AtomicReference rawResult = new AtomicReference(null); + int computeN() { return computeN.get(); } + int onCompletionN() { return onCompletionN.get(); } + int onExceptionalCompletionN() { return onExceptionalCompletionN.get(); } + int setRawResultN() { return setRawResultN.get(); } + + CheckedCC() { super(); } + CheckedCC(CountedCompleter p) { super(p); } + CheckedCC(CountedCompleter p, int n) { super(p, n); } + abstract void realCompute(); + public final void compute() { + computeN.incrementAndGet(); + realCompute(); + } + public void onCompletion(CountedCompleter caller) { + onCompletionN.incrementAndGet(); + super.onCompletion(caller); + } + public boolean onExceptionalCompletion(Throwable ex, + CountedCompleter caller) { + onExceptionalCompletionN.incrementAndGet(); + assertNotNull(ex); + assertTrue(isCompletedAbnormally()); + assertTrue(super.onExceptionalCompletion(ex, caller)); + return true; + } + protected void setRawResult(Object t) { + setRawResultN.incrementAndGet(); + rawResult.set(t); + super.setRawResult(t); + } + void checkIncomplete() { + assertEquals(0, computeN()); + assertEquals(0, onCompletionN()); + assertEquals(0, onExceptionalCompletionN()); + assertEquals(0, setRawResultN()); + checkNotDone(this); + } + void checkCompletes(Object rawResult) { + checkIncomplete(); + int pendingCount = getPendingCount(); + complete(rawResult); + assertEquals(pendingCount, getPendingCount()); + assertEquals(0, computeN()); + assertEquals(1, onCompletionN()); + assertEquals(0, onExceptionalCompletionN()); + assertEquals(1, setRawResultN()); + assertSame(rawResult, this.rawResult.get()); + checkCompletedNormally(this); + } + void checkCompletesExceptionally(Throwable ex) { + checkIncomplete(); + completeExceptionally(ex); + checkCompletedExceptionally(ex); + } + void checkCompletedExceptionally(Throwable ex) { + assertEquals(0, computeN()); + assertEquals(0, onCompletionN()); + assertEquals(1, onExceptionalCompletionN()); + assertEquals(0, setRawResultN()); + assertNull(this.rawResult.get()); + checkCompletedAbnormally(this, ex); + } + } + + final class NoopCC extends CheckedCC { + NoopCC() { super(); } + NoopCC(CountedCompleter p) { super(p); } + NoopCC(CountedCompleter p, int initialPendingCount) { + super(p, initialPendingCount); + } + protected void realCompute() {} + } + + /** + * A newly constructed CountedCompleter is not completed; + * complete() causes completion. pendingCount is ignored. + */ + public void testComplete() { + for (Object x : new Object[] { Boolean.TRUE, null }) { + for (int pendingCount : new int[] { 0, 42 }) { + testComplete(new NoopCC(), x, pendingCount); + testComplete(new NoopCC(new NoopCC()), x, pendingCount); + } + } + } + void testComplete(NoopCC cc, Object x, int pendingCount) { + cc.setPendingCount(pendingCount); + cc.checkCompletes(x); + assertEquals(pendingCount, cc.getPendingCount()); + } + + /** + * completeExceptionally completes exceptionally + */ + public void testCompleteExceptionally() { + new NoopCC() + .checkCompletesExceptionally(new FJException()); + new NoopCC(new NoopCC()) + .checkCompletesExceptionally(new FJException()); + } + + /** + * completeExceptionally(null) surprisingly has the same effect as + * completeExceptionally(new RuntimeException()) + */ + public void testCompleteExceptionally_null() { + NoopCC a = new NoopCC(); + a.completeExceptionally(null); + try { + a.invoke(); + shouldThrow(); + } catch (RuntimeException success) { + assertSame(success.getClass(), RuntimeException.class); + assertNull(success.getCause()); + a.checkCompletedExceptionally(success); + } + } + + /** + * setPendingCount sets the reported pending count + */ + public void testSetPendingCount() { + NoopCC a = new NoopCC(); + assertEquals(0, a.getPendingCount()); + int[] vals = { + -1, 0, 1, + Integer.MIN_VALUE, + Integer.MAX_VALUE, + }; + for (int val : vals) { + a.setPendingCount(val); + assertEquals(val, a.getPendingCount()); + } + } + + /** + * addToPendingCount adds to the reported pending count + */ + public void testAddToPendingCount() { + NoopCC a = new NoopCC(); + assertEquals(0, a.getPendingCount()); + a.addToPendingCount(1); + assertEquals(1, a.getPendingCount()); + a.addToPendingCount(27); + assertEquals(28, a.getPendingCount()); + a.addToPendingCount(-28); + assertEquals(0, a.getPendingCount()); + } + + /** + * decrementPendingCountUnlessZero decrements reported pending + * count unless zero + */ + public void testDecrementPendingCountUnlessZero() { + NoopCC a = new NoopCC(null, 2); + assertEquals(2, a.getPendingCount()); + assertEquals(2, a.decrementPendingCountUnlessZero()); + assertEquals(1, a.getPendingCount()); + assertEquals(1, a.decrementPendingCountUnlessZero()); + assertEquals(0, a.getPendingCount()); + assertEquals(0, a.decrementPendingCountUnlessZero()); + assertEquals(0, a.getPendingCount()); + a.setPendingCount(-1); + assertEquals(-1, a.decrementPendingCountUnlessZero()); + assertEquals(-2, a.getPendingCount()); + } + + /** + * compareAndSetPendingCount compares and sets the reported + * pending count + */ + public void testCompareAndSetPendingCount() { + NoopCC a = new NoopCC(); + assertEquals(0, a.getPendingCount()); + assertTrue(a.compareAndSetPendingCount(0, 1)); + assertEquals(1, a.getPendingCount()); + assertTrue(a.compareAndSetPendingCount(1, 2)); + assertEquals(2, a.getPendingCount()); + assertFalse(a.compareAndSetPendingCount(1, 3)); + assertEquals(2, a.getPendingCount()); + } + + /** + * getCompleter returns parent or null if at root + */ + public void testGetCompleter() { + NoopCC a = new NoopCC(); + assertNull(a.getCompleter()); + CountedCompleter b = new NoopCC(a); + assertSame(a, b.getCompleter()); + CountedCompleter c = new NoopCC(b); + assertSame(b, c.getCompleter()); + } + + /** + * getRoot returns self if no parent, else parent's root + */ + public void testGetRoot() { + NoopCC a = new NoopCC(); + NoopCC b = new NoopCC(a); + NoopCC c = new NoopCC(b); + assertSame(a, a.getRoot()); + assertSame(a, b.getRoot()); + assertSame(a, c.getRoot()); + } + + /** + * tryComplete decrements pending count unless zero, in which case + * causes completion + */ + public void testTryComplete() { + NoopCC a = new NoopCC(); + assertEquals(0, a.getPendingCount()); + int n = 3; + a.setPendingCount(n); + for (; n > 0; n--) { + assertEquals(n, a.getPendingCount()); + a.tryComplete(); + a.checkIncomplete(); + assertEquals(n - 1, a.getPendingCount()); + } + a.tryComplete(); + assertEquals(0, a.computeN()); + assertEquals(1, a.onCompletionN()); + assertEquals(0, a.onExceptionalCompletionN()); + assertEquals(0, a.setRawResultN()); + checkCompletedNormally(a); + } + + /** + * propagateCompletion decrements pending count unless zero, in + * which case causes completion, without invoking onCompletion + */ + public void testPropagateCompletion() { + NoopCC a = new NoopCC(); + assertEquals(0, a.getPendingCount()); + int n = 3; + a.setPendingCount(n); + for (; n > 0; n--) { + assertEquals(n, a.getPendingCount()); + a.propagateCompletion(); + a.checkIncomplete(); + assertEquals(n - 1, a.getPendingCount()); + } + a.propagateCompletion(); + assertEquals(0, a.computeN()); + assertEquals(0, a.onCompletionN()); + assertEquals(0, a.onExceptionalCompletionN()); + assertEquals(0, a.setRawResultN()); + checkCompletedNormally(a); + } + + /** + * firstComplete returns this if pending count is zero else null + */ + public void testFirstComplete() { + NoopCC a = new NoopCC(); + a.setPendingCount(1); + assertNull(a.firstComplete()); + a.checkIncomplete(); + assertSame(a, a.firstComplete()); + a.checkIncomplete(); + } + + /** + * firstComplete.nextComplete returns parent if pending count is + * zero else null + */ + public void testNextComplete() { + NoopCC a = new NoopCC(); + NoopCC b = new NoopCC(a); + a.setPendingCount(1); + b.setPendingCount(1); + assertNull(b.firstComplete()); + assertSame(b, b.firstComplete()); + assertNull(b.nextComplete()); + a.checkIncomplete(); + b.checkIncomplete(); + assertSame(a, b.nextComplete()); + assertSame(a, b.nextComplete()); + a.checkIncomplete(); + b.checkIncomplete(); + assertNull(a.nextComplete()); + b.checkIncomplete(); + checkCompletedNormally(a); + } + + /** + * quietlyCompleteRoot completes root task and only root task + */ + public void testQuietlyCompleteRoot() { + NoopCC a = new NoopCC(); + NoopCC b = new NoopCC(a); + NoopCC c = new NoopCC(b); + a.setPendingCount(1); + b.setPendingCount(1); + c.setPendingCount(1); + c.quietlyCompleteRoot(); + assertTrue(a.isDone()); + assertFalse(b.isDone()); + assertFalse(c.isDone()); + } + + // Invocation tests use some interdependent task classes + // to better test propagation etc + + /** + * Version of Fibonacci with different classes for left vs right forks + */ + abstract class CCF extends CheckedCC { + int number; + int rnumber; + + public CCF(CountedCompleter parent, int n) { + super(parent, 1); + this.number = n; + } + + protected final void realCompute() { + CCF f = this; + int n = number; + while (n >= 2) { + new RCCF(f, n - 2).fork(); + f = new LCCF(f, --n); + } + f.complete(null); + } + } + + final class LCCF extends CCF { + public LCCF(int n) { this(null, n); } + public LCCF(CountedCompleter parent, int n) { + super(parent, n); + } + public final void onCompletion(CountedCompleter caller) { + super.onCompletion(caller); + CCF p = (CCF)getCompleter(); + int n = number + rnumber; + if (p != null) + p.number = n; + else + number = n; + } + } + final class RCCF extends CCF { + public RCCF(CountedCompleter parent, int n) { + super(parent, n); + } + public final void onCompletion(CountedCompleter caller) { + super.onCompletion(caller); + CCF p = (CCF)getCompleter(); + int n = number + rnumber; + if (p != null) + p.rnumber = n; + else + number = n; + } + } + + // Version of CCF with forced failure in left completions + abstract class FailingCCF extends CheckedCC { + int number; + int rnumber; + + public FailingCCF(CountedCompleter parent, int n) { + super(parent, 1); + this.number = n; + } + + protected final void realCompute() { + FailingCCF f = this; + int n = number; + while (n >= 2) { + new RFCCF(f, n - 2).fork(); + f = new LFCCF(f, --n); + } + f.complete(null); + } + } + + final class LFCCF extends FailingCCF { + public LFCCF(int n) { this(null, n); } + public LFCCF(CountedCompleter parent, int n) { + super(parent, n); + } + public final void onCompletion(CountedCompleter caller) { + super.onCompletion(caller); + FailingCCF p = (FailingCCF)getCompleter(); + int n = number + rnumber; + if (p != null) + p.number = n; + else + number = n; + } + } + final class RFCCF extends FailingCCF { + public RFCCF(CountedCompleter parent, int n) { + super(parent, n); + } + public final void onCompletion(CountedCompleter caller) { + super.onCompletion(caller); + completeExceptionally(new FJException()); + } + } + + /** + * invoke returns when task completes normally. + * isCompletedAbnormally and isCancelled return false for normally + * completed tasks; getRawResult returns null. + */ + public void testInvoke() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + assertNull(f.invoke()); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * quietlyInvoke task returns when task completes normally. + * isCompletedAbnormally and isCancelled return false for normally + * completed tasks + */ + public void testQuietlyInvoke() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + f.quietlyInvoke(); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * join of a forked task returns when task completes + */ + public void testForkJoin() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + assertSame(f, f.fork()); + assertNull(f.join()); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * get of a forked task returns when task completes + */ + public void testForkGet() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + CCF f = new LCCF(8); + assertSame(f, f.fork()); + assertNull(f.get()); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * timed get of a forked task returns when task completes + */ + public void testForkTimedGet() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + CCF f = new LCCF(8); + assertSame(f, f.fork()); + assertNull(f.get(LONG_DELAY_MS, MILLISECONDS)); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * timed get with null time unit throws NPE + */ + public void testForkTimedGetNPE() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + CCF f = new LCCF(8); + assertSame(f, f.fork()); + try { + f.get(5L, null); + shouldThrow(); + } catch (NullPointerException success) {} + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * quietlyJoin of a forked task returns when task completes + */ + public void testForkQuietlyJoin() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + assertSame(f, f.fork()); + f.quietlyJoin(); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * helpQuiesce returns when tasks are complete. + * getQueuedTaskCount returns 0 when quiescent + */ + public void testForkHelpQuiesce() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + assertSame(f, f.fork()); + helpQuiesce(); + assertEquals(21, f.number); + assertEquals(0, getQueuedTaskCount()); + checkCompletedNormally(f); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invoke task throws exception when task completes abnormally + */ + public void testAbnormalInvoke() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingCCF f = new LFCCF(8); + try { + f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * quietlyInvoke task returns when task completes abnormally + */ + public void testAbnormalQuietlyInvoke() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingCCF f = new LFCCF(8); + f.quietlyInvoke(); + assertTrue(f.getException() instanceof FJException); + checkCompletedAbnormally(f, f.getException()); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * join of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkJoin() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingCCF f = new LFCCF(8); + assertSame(f, f.fork()); + try { + f.join(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * get of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkGet() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FailingCCF f = new LFCCF(8); + assertSame(f, f.fork()); + try { + f.get(); + shouldThrow(); + } catch (ExecutionException success) { + Throwable cause = success.getCause(); + assertTrue(cause instanceof FJException); + checkCompletedAbnormally(f, cause); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * timed get of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkTimedGet() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FailingCCF f = new LFCCF(8); + assertSame(f, f.fork()); + try { + f.get(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (ExecutionException success) { + Throwable cause = success.getCause(); + assertTrue(cause instanceof FJException); + checkCompletedAbnormally(f, cause); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * quietlyJoin of a forked task returns when task completes abnormally + */ + public void testAbnormalForkQuietlyJoin() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingCCF f = new LFCCF(8); + assertSame(f, f.fork()); + f.quietlyJoin(); + assertTrue(f.getException() instanceof FJException); + checkCompletedAbnormally(f, f.getException()); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invoke task throws exception when task cancelled + */ + public void testCancelledInvoke() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + assertTrue(f.cancel(true)); + try { + f.invoke(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * join of a forked task throws exception when task cancelled + */ + public void testCancelledForkJoin() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.join(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * get of a forked task throws exception when task cancelled + */ + public void testCancelledForkGet() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + CCF f = new LCCF(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.get(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * timed get of a forked task throws exception when task cancelled + */ + public void testCancelledForkTimedGet() throws Exception { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + CCF f = new LCCF(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.get(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * quietlyJoin of a forked task returns when task cancelled + */ + public void testCancelledForkQuietlyJoin() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + f.quietlyJoin(); + checkCancelled(f); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * getPool of executing task returns its pool + */ + public void testGetPool() { + final ForkJoinPool mainPool = mainPool(); + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + assertSame(mainPool, getPool()); + }}; + testInvokeOnPool(mainPool, a); + } + + /** + * getPool of non-FJ task returns null + */ + public void testGetPool2() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + assertNull(getPool()); + }}; + assertNull(a.invoke()); + } + + /** + * inForkJoinPool of executing task returns true + */ + public void testInForkJoinPool() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + assertTrue(inForkJoinPool()); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * inForkJoinPool of non-FJ task returns false + */ + public void testInForkJoinPool2() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + assertFalse(inForkJoinPool()); + }}; + assertNull(a.invoke()); + } + + /** + * setRawResult(null) succeeds + */ + public void testSetRawResult() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + setRawResult(null); + assertNull(getRawResult()); + }}; + assertNull(a.invoke()); + } + + /** + * invoke task throws exception after invoking completeExceptionally + */ + public void testCompleteExceptionally2() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF n = new LCCF(8); + CCF f = new LCCF(n, 8); + FJException ex = new FJException(); + f.completeExceptionally(ex); + f.checkCompletedExceptionally(ex); + n.checkCompletedExceptionally(ex); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(t1, t2) invokes all task arguments + */ + public void testInvokeAll2() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + CCF g = new LCCF(9); + invokeAll(f, g); + assertEquals(21, f.number); + assertEquals(34, g.number); + checkCompletedNormally(f); + checkCompletedNormally(g); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(tasks) with 1 argument invokes task + */ + public void testInvokeAll1() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + invokeAll(f); + checkCompletedNormally(f); + assertEquals(21, f.number); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(tasks) with > 2 argument invokes tasks + */ + public void testInvokeAll3() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + CCF g = new LCCF(9); + CCF h = new LCCF(7); + invokeAll(f, g, h); + assertEquals(21, f.number); + assertEquals(34, g.number); + assertEquals(13, h.number); + checkCompletedNormally(f); + checkCompletedNormally(g); + checkCompletedNormally(h); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(collection) invokes all tasks in the collection + */ + public void testInvokeAllCollection() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + CCF g = new LCCF(9); + CCF h = new LCCF(7); + HashSet set = new HashSet(); + set.add(f); + set.add(g); + set.add(h); + invokeAll(set); + assertEquals(21, f.number); + assertEquals(34, g.number); + assertEquals(13, h.number); + checkCompletedNormally(f); + checkCompletedNormally(g); + checkCompletedNormally(h); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(tasks) with any null task throws NPE + */ + public void testInvokeAllNPE() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + CCF g = new LCCF(9); + CCF h = null; + try { + invokeAll(f, g, h); + shouldThrow(); + } catch (NullPointerException success) {} + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(t1, t2) throw exception if any task does + */ + public void testAbnormalInvokeAll2() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + FailingCCF g = new LFCCF(9); + try { + invokeAll(f, g); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(tasks) with 1 argument throws exception if task does + */ + public void testAbnormalInvokeAll1() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingCCF g = new LFCCF(9); + try { + invokeAll(g); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(tasks) with > 2 argument throws exception if any task does + */ + public void testAbnormalInvokeAll3() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + FailingCCF g = new LFCCF(9); + CCF h = new LCCF(7); + try { + invokeAll(f, g, h); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(collection) throws exception if any task does + */ + public void testAbnormalInvokeAllCollection() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingCCF f = new LFCCF(8); + CCF g = new LCCF(9); + CCF h = new LCCF(7); + HashSet set = new HashSet(); + set.add(f); + set.add(g); + set.add(h); + try { + invokeAll(set); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * tryUnfork returns true for most recent unexecuted task, + * and suppresses execution + */ + public void testTryUnfork() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF g = new LCCF(9); + assertSame(g, g.fork()); + CCF f = new LCCF(8); + assertSame(f, f.fork()); + assertTrue(f.tryUnfork()); + helpQuiesce(); + checkNotDone(f); + checkCompletedNormally(g); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * getSurplusQueuedTaskCount returns > 0 when + * there are more tasks than threads + */ + public void testGetSurplusQueuedTaskCount() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF h = new LCCF(7); + assertSame(h, h.fork()); + CCF g = new LCCF(9); + assertSame(g, g.fork()); + CCF f = new LCCF(8); + assertSame(f, f.fork()); + assertTrue(getSurplusQueuedTaskCount() > 0); + helpQuiesce(); + assertEquals(0, getSurplusQueuedTaskCount()); + checkCompletedNormally(f); + checkCompletedNormally(g); + checkCompletedNormally(h); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * peekNextLocalTask returns most recent unexecuted task. + */ + public void testPeekNextLocalTask() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF g = new LCCF(9); + assertSame(g, g.fork()); + CCF f = new LCCF(8); + assertSame(f, f.fork()); + assertSame(f, peekNextLocalTask()); + assertNull(f.join()); + checkCompletedNormally(f); + helpQuiesce(); + checkCompletedNormally(g); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * pollNextLocalTask returns most recent unexecuted task without + * executing it + */ + public void testPollNextLocalTask() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF g = new LCCF(9); + assertSame(g, g.fork()); + CCF f = new LCCF(8); + assertSame(f, f.fork()); + assertSame(f, pollNextLocalTask()); + helpQuiesce(); + checkNotDone(f); + assertEquals(34, g.number); + checkCompletedNormally(g); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * pollTask returns an unexecuted task without executing it + */ + public void testPollTask() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF g = new LCCF(9); + assertSame(g, g.fork()); + CCF f = new LCCF(8); + assertSame(f, f.fork()); + assertSame(f, pollTask()); + helpQuiesce(); + checkNotDone(f); + checkCompletedNormally(g); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * peekNextLocalTask returns least recent unexecuted task in async mode + */ + public void testPeekNextLocalTaskAsync() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF g = new LCCF(9); + assertSame(g, g.fork()); + CCF f = new LCCF(8); + assertSame(f, f.fork()); + assertSame(g, peekNextLocalTask()); + assertNull(f.join()); + helpQuiesce(); + checkCompletedNormally(f); + assertEquals(34, g.number); + checkCompletedNormally(g); + }}; + testInvokeOnPool(asyncSingletonPool(), a); + } + + /** + * pollNextLocalTask returns least recent unexecuted task without + * executing it, in async mode + */ + public void testPollNextLocalTaskAsync() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF g = new LCCF(9); + assertSame(g, g.fork()); + CCF f = new LCCF(8); + assertSame(f, f.fork()); + assertSame(g, pollNextLocalTask()); + helpQuiesce(); + assertEquals(21, f.number); + checkCompletedNormally(f); + checkNotDone(g); + }}; + testInvokeOnPool(asyncSingletonPool(), a); + } + + /** + * pollTask returns an unexecuted task without executing it, in + * async mode + */ + public void testPollTaskAsync() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF g = new LCCF(9); + assertSame(g, g.fork()); + CCF f = new LCCF(8); + assertSame(f, f.fork()); + assertSame(g, pollTask()); + helpQuiesce(); + assertEquals(21, f.number); + checkCompletedNormally(f); + checkNotDone(g); + }}; + testInvokeOnPool(asyncSingletonPool(), a); + } + + // versions for singleton pools + + /** + * invoke returns when task completes normally. + * isCompletedAbnormally and isCancelled return false for normally + * completed tasks; getRawResult returns null. + */ + public void testInvokeSingleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + assertNull(f.invoke()); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * quietlyInvoke task returns when task completes normally. + * isCompletedAbnormally and isCancelled return false for normally + * completed tasks + */ + public void testQuietlyInvokeSingleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + f.quietlyInvoke(); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * join of a forked task returns when task completes + */ + public void testForkJoinSingleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + assertSame(f, f.fork()); + assertNull(f.join()); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * get of a forked task returns when task completes + */ + public void testForkGetSingleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + CCF f = new LCCF(8); + assertSame(f, f.fork()); + assertNull(f.get()); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * timed get of a forked task returns when task completes + */ + public void testForkTimedGetSingleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + CCF f = new LCCF(8); + assertSame(f, f.fork()); + assertNull(f.get(LONG_DELAY_MS, MILLISECONDS)); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * timed get with null time unit throws NPE + */ + public void testForkTimedGetNPESingleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + CCF f = new LCCF(8); + assertSame(f, f.fork()); + try { + f.get(5L, null); + shouldThrow(); + } catch (NullPointerException success) {} + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * quietlyJoin of a forked task returns when task completes + */ + public void testForkQuietlyJoinSingleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + assertSame(f, f.fork()); + f.quietlyJoin(); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * helpQuiesce returns when tasks are complete. + * getQueuedTaskCount returns 0 when quiescent + */ + public void testForkHelpQuiesceSingleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + assertSame(f, f.fork()); + helpQuiesce(); + assertEquals(0, getQueuedTaskCount()); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * invoke task throws exception when task completes abnormally + */ + public void testAbnormalInvokeSingleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingCCF f = new LFCCF(8); + try { + f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * quietlyInvoke task returns when task completes abnormally + */ + public void testAbnormalQuietlyInvokeSingleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingCCF f = new LFCCF(8); + f.quietlyInvoke(); + assertTrue(f.getException() instanceof FJException); + checkCompletedAbnormally(f, f.getException()); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * join of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkJoinSingleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingCCF f = new LFCCF(8); + assertSame(f, f.fork()); + try { + f.join(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * get of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkGetSingleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FailingCCF f = new LFCCF(8); + assertSame(f, f.fork()); + try { + f.get(); + shouldThrow(); + } catch (ExecutionException success) { + Throwable cause = success.getCause(); + assertTrue(cause instanceof FJException); + checkCompletedAbnormally(f, cause); + } + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * timed get of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkTimedGetSingleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FailingCCF f = new LFCCF(8); + assertSame(f, f.fork()); + try { + f.get(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (ExecutionException success) { + Throwable cause = success.getCause(); + assertTrue(cause instanceof FJException); + checkCompletedAbnormally(f, cause); + } + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * quietlyJoin of a forked task returns when task completes abnormally + */ + public void testAbnormalForkQuietlyJoinSingleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingCCF f = new LFCCF(8); + assertSame(f, f.fork()); + f.quietlyJoin(); + assertTrue(f.getException() instanceof FJException); + checkCompletedAbnormally(f, f.getException()); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * invoke task throws exception when task cancelled + */ + public void testCancelledInvokeSingleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + assertTrue(f.cancel(true)); + try { + f.invoke(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * join of a forked task throws exception when task cancelled + */ + public void testCancelledForkJoinSingleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.join(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * get of a forked task throws exception when task cancelled + */ + public void testCancelledForkGetSingleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + CCF f = new LCCF(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.get(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * timed get of a forked task throws exception when task cancelled + */ + public void testCancelledForkTimedGetSingleton() throws Exception { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + CCF f = new LCCF(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.get(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * quietlyJoin of a forked task returns when task cancelled + */ + public void testCancelledForkQuietlyJoinSingleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + f.quietlyJoin(); + checkCancelled(f); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * invoke task throws exception after invoking completeExceptionally + */ + public void testCompleteExceptionallySingleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF n = new LCCF(8); + CCF f = new LCCF(n, 8); + FJException ex = new FJException(); + f.completeExceptionally(ex); + f.checkCompletedExceptionally(ex); + n.checkCompletedExceptionally(ex); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * invokeAll(t1, t2) invokes all task arguments + */ + public void testInvokeAll2Singleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + CCF g = new LCCF(9); + invokeAll(f, g); + assertEquals(21, f.number); + assertEquals(34, g.number); + checkCompletedNormally(f); + checkCompletedNormally(g); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * invokeAll(tasks) with 1 argument invokes task + */ + public void testInvokeAll1Singleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + invokeAll(f); + checkCompletedNormally(f); + assertEquals(21, f.number); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * invokeAll(tasks) with > 2 argument invokes tasks + */ + public void testInvokeAll3Singleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + CCF g = new LCCF(9); + CCF h = new LCCF(7); + invokeAll(f, g, h); + assertEquals(21, f.number); + assertEquals(34, g.number); + assertEquals(13, h.number); + checkCompletedNormally(f); + checkCompletedNormally(g); + checkCompletedNormally(h); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * invokeAll(collection) invokes all tasks in the collection + */ + public void testInvokeAllCollectionSingleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + CCF g = new LCCF(9); + CCF h = new LCCF(7); + HashSet set = new HashSet(); + set.add(f); + set.add(g); + set.add(h); + invokeAll(set); + assertEquals(21, f.number); + assertEquals(34, g.number); + assertEquals(13, h.number); + checkCompletedNormally(f); + checkCompletedNormally(g); + checkCompletedNormally(h); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * invokeAll(tasks) with any null task throws NPE + */ + public void testInvokeAllNPESingleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + CCF g = new LCCF(9); + CCF h = null; + try { + invokeAll(f, g, h); + shouldThrow(); + } catch (NullPointerException success) {} + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * invokeAll(t1, t2) throw exception if any task does + */ + public void testAbnormalInvokeAll2Singleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + FailingCCF g = new LFCCF(9); + try { + invokeAll(f, g); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * invokeAll(tasks) with 1 argument throws exception if task does + */ + public void testAbnormalInvokeAll1Singleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingCCF g = new LFCCF(9); + try { + invokeAll(g); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * invokeAll(tasks) with > 2 argument throws exception if any task does + */ + public void testAbnormalInvokeAll3Singleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(8); + FailingCCF g = new LFCCF(9); + CCF h = new LCCF(7); + try { + invokeAll(f, g, h); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * invokeAll(collection) throws exception if any task does + */ + public void testAbnormalInvokeAllCollectionSingleton() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingCCF f = new LFCCF(8); + CCF g = new LCCF(9); + CCF h = new LCCF(7); + HashSet set = new HashSet(); + set.add(f); + set.add(g); + set.add(h); + try { + invokeAll(set); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(singletonPool(), a); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/CyclicBarrierTest.java b/jdk/test/java/util/concurrent/tck/CyclicBarrierTest.java new file mode 100644 index 00000000000..bfae2ee323b --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/CyclicBarrierTest.java @@ -0,0 +1,491 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class CyclicBarrierTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(CyclicBarrierTest.class); + } + + private volatile int countAction; + private class MyAction implements Runnable { + public void run() { ++countAction; } + } + + /** + * Spin-waits till the number of waiters == numberOfWaiters. + */ + void awaitNumberWaiting(CyclicBarrier barrier, int numberOfWaiters) { + long startTime = System.nanoTime(); + while (barrier.getNumberWaiting() != numberOfWaiters) { + if (millisElapsedSince(startTime) > LONG_DELAY_MS) + fail("timed out"); + Thread.yield(); + } + } + + /** + * Creating with negative parties throws IAE + */ + public void testConstructor1() { + try { + new CyclicBarrier(-1, (Runnable)null); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Creating with negative parties and no action throws IAE + */ + public void testConstructor2() { + try { + new CyclicBarrier(-1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * getParties returns the number of parties given in constructor + */ + public void testGetParties() { + CyclicBarrier b = new CyclicBarrier(2); + assertEquals(2, b.getParties()); + assertEquals(0, b.getNumberWaiting()); + } + + /** + * A 1-party barrier triggers after single await + */ + public void testSingleParty() throws Exception { + CyclicBarrier b = new CyclicBarrier(1); + assertEquals(1, b.getParties()); + assertEquals(0, b.getNumberWaiting()); + b.await(); + b.await(); + assertEquals(0, b.getNumberWaiting()); + } + + /** + * The supplied barrier action is run at barrier + */ + public void testBarrierAction() throws Exception { + countAction = 0; + CyclicBarrier b = new CyclicBarrier(1, new MyAction()); + assertEquals(1, b.getParties()); + assertEquals(0, b.getNumberWaiting()); + b.await(); + b.await(); + assertEquals(0, b.getNumberWaiting()); + assertEquals(2, countAction); + } + + /** + * A 2-party/thread barrier triggers after both threads invoke await + */ + public void testTwoParties() throws Exception { + final CyclicBarrier b = new CyclicBarrier(2); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws Exception { + b.await(); + b.await(); + b.await(); + b.await(); + }}); + + b.await(); + b.await(); + b.await(); + b.await(); + awaitTermination(t); + } + + /** + * An interruption in one party causes others waiting in await to + * throw BrokenBarrierException + */ + public void testAwait1_Interrupted_BrokenBarrier() { + final CyclicBarrier c = new CyclicBarrier(3); + final CountDownLatch pleaseInterrupt = new CountDownLatch(2); + Thread t1 = new ThreadShouldThrow(InterruptedException.class) { + public void realRun() throws Exception { + pleaseInterrupt.countDown(); + c.await(); + }}; + Thread t2 = new ThreadShouldThrow(BrokenBarrierException.class) { + public void realRun() throws Exception { + pleaseInterrupt.countDown(); + c.await(); + }}; + + t1.start(); + t2.start(); + await(pleaseInterrupt); + t1.interrupt(); + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * An interruption in one party causes others waiting in timed await to + * throw BrokenBarrierException + */ + public void testAwait2_Interrupted_BrokenBarrier() throws Exception { + final CyclicBarrier c = new CyclicBarrier(3); + final CountDownLatch pleaseInterrupt = new CountDownLatch(2); + Thread t1 = new ThreadShouldThrow(InterruptedException.class) { + public void realRun() throws Exception { + pleaseInterrupt.countDown(); + c.await(LONG_DELAY_MS, MILLISECONDS); + }}; + Thread t2 = new ThreadShouldThrow(BrokenBarrierException.class) { + public void realRun() throws Exception { + pleaseInterrupt.countDown(); + c.await(LONG_DELAY_MS, MILLISECONDS); + }}; + + t1.start(); + t2.start(); + await(pleaseInterrupt); + t1.interrupt(); + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * A timeout in timed await throws TimeoutException + */ + public void testAwait3_TimeoutException() throws InterruptedException { + final CyclicBarrier c = new CyclicBarrier(2); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws Exception { + long startTime = System.nanoTime(); + try { + c.await(timeoutMillis(), MILLISECONDS); + shouldThrow(); + } catch (TimeoutException success) {} + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + }}); + + awaitTermination(t); + } + + /** + * A timeout in one party causes others waiting in timed await to + * throw BrokenBarrierException + */ + public void testAwait4_Timeout_BrokenBarrier() throws InterruptedException { + final CyclicBarrier c = new CyclicBarrier(3); + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() throws Exception { + try { + c.await(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (BrokenBarrierException success) {} + }}); + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() throws Exception { + awaitNumberWaiting(c, 1); + long startTime = System.nanoTime(); + try { + c.await(timeoutMillis(), MILLISECONDS); + shouldThrow(); + } catch (TimeoutException success) {} + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + }}); + + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * A timeout in one party causes others waiting in await to + * throw BrokenBarrierException + */ + public void testAwait5_Timeout_BrokenBarrier() throws InterruptedException { + final CyclicBarrier c = new CyclicBarrier(3); + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() throws Exception { + try { + c.await(); + shouldThrow(); + } catch (BrokenBarrierException success) {} + }}); + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() throws Exception { + awaitNumberWaiting(c, 1); + long startTime = System.nanoTime(); + try { + c.await(timeoutMillis(), MILLISECONDS); + shouldThrow(); + } catch (TimeoutException success) {} + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + }}); + + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * A reset of an active barrier causes waiting threads to throw + * BrokenBarrierException + */ + public void testReset_BrokenBarrier() throws InterruptedException { + final CyclicBarrier c = new CyclicBarrier(3); + final CountDownLatch pleaseReset = new CountDownLatch(2); + Thread t1 = new ThreadShouldThrow(BrokenBarrierException.class) { + public void realRun() throws Exception { + pleaseReset.countDown(); + c.await(); + }}; + Thread t2 = new ThreadShouldThrow(BrokenBarrierException.class) { + public void realRun() throws Exception { + pleaseReset.countDown(); + c.await(); + }}; + + t1.start(); + t2.start(); + await(pleaseReset); + + awaitNumberWaiting(c, 2); + c.reset(); + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * A reset before threads enter barrier does not throw + * BrokenBarrierException + */ + public void testReset_NoBrokenBarrier() throws Exception { + final CyclicBarrier c = new CyclicBarrier(3); + c.reset(); + + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() throws Exception { + c.await(); + }}); + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() throws Exception { + c.await(); + }}); + + c.await(); + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * All threads block while a barrier is broken. + */ + public void testReset_Leakage() throws InterruptedException { + final CyclicBarrier c = new CyclicBarrier(2); + final AtomicBoolean done = new AtomicBoolean(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + while (!done.get()) { + try { + while (c.isBroken()) + c.reset(); + + c.await(); + shouldThrow(); + } + catch (BrokenBarrierException ok) {} + catch (InterruptedException ok) {} + }}}); + + for (int i = 0; i < 4; i++) { + delay(timeoutMillis()); + t.interrupt(); + } + done.set(true); + t.interrupt(); + awaitTermination(t); + } + + /** + * Reset of a non-broken barrier does not break barrier + */ + public void testResetWithoutBreakage() throws Exception { + final CyclicBarrier barrier = new CyclicBarrier(3); + for (int i = 0; i < 3; i++) { + final CyclicBarrier start = new CyclicBarrier(3); + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() throws Exception { + start.await(); + barrier.await(); + }}); + + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() throws Exception { + start.await(); + barrier.await(); + }}); + + start.await(); + barrier.await(); + awaitTermination(t1); + awaitTermination(t2); + assertFalse(barrier.isBroken()); + assertEquals(0, barrier.getNumberWaiting()); + if (i == 1) barrier.reset(); + assertFalse(barrier.isBroken()); + assertEquals(0, barrier.getNumberWaiting()); + } + } + + /** + * Reset of a barrier after interruption reinitializes it. + */ + public void testResetAfterInterrupt() throws Exception { + final CyclicBarrier barrier = new CyclicBarrier(3); + for (int i = 0; i < 2; i++) { + final CyclicBarrier start = new CyclicBarrier(3); + Thread t1 = new ThreadShouldThrow(InterruptedException.class) { + public void realRun() throws Exception { + start.await(); + barrier.await(); + }}; + + Thread t2 = new ThreadShouldThrow(BrokenBarrierException.class) { + public void realRun() throws Exception { + start.await(); + barrier.await(); + }}; + + t1.start(); + t2.start(); + start.await(); + t1.interrupt(); + awaitTermination(t1); + awaitTermination(t2); + assertTrue(barrier.isBroken()); + assertEquals(0, barrier.getNumberWaiting()); + barrier.reset(); + assertFalse(barrier.isBroken()); + assertEquals(0, barrier.getNumberWaiting()); + } + } + + /** + * Reset of a barrier after timeout reinitializes it. + */ + public void testResetAfterTimeout() throws Exception { + final CyclicBarrier barrier = new CyclicBarrier(3); + for (int i = 0; i < 2; i++) { + assertEquals(0, barrier.getNumberWaiting()); + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() throws Exception { + try { + barrier.await(); + shouldThrow(); + } catch (BrokenBarrierException success) {} + }}); + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() throws Exception { + awaitNumberWaiting(barrier, 1); + long startTime = System.nanoTime(); + try { + barrier.await(timeoutMillis(), MILLISECONDS); + shouldThrow(); + } catch (TimeoutException success) {} + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + }}); + + awaitTermination(t1); + awaitTermination(t2); + assertEquals(0, barrier.getNumberWaiting()); + assertTrue(barrier.isBroken()); + assertEquals(0, barrier.getNumberWaiting()); + barrier.reset(); + assertFalse(barrier.isBroken()); + assertEquals(0, barrier.getNumberWaiting()); + } + } + + /** + * Reset of a barrier after a failed command reinitializes it. + */ + public void testResetAfterCommandException() throws Exception { + final CyclicBarrier barrier = + new CyclicBarrier(3, new Runnable() { + public void run() { + throw new NullPointerException(); }}); + for (int i = 0; i < 2; i++) { + final CyclicBarrier start = new CyclicBarrier(3); + Thread t1 = new ThreadShouldThrow(BrokenBarrierException.class) { + public void realRun() throws Exception { + start.await(); + barrier.await(); + }}; + + Thread t2 = new ThreadShouldThrow(BrokenBarrierException.class) { + public void realRun() throws Exception { + start.await(); + barrier.await(); + }}; + + t1.start(); + t2.start(); + start.await(); + awaitNumberWaiting(barrier, 2); + try { + barrier.await(); + shouldThrow(); + } catch (NullPointerException success) {} + awaitTermination(t1); + awaitTermination(t2); + assertTrue(barrier.isBroken()); + assertEquals(0, barrier.getNumberWaiting()); + barrier.reset(); + assertFalse(barrier.isBroken()); + assertEquals(0, barrier.getNumberWaiting()); + } + } +} diff --git a/jdk/test/java/util/concurrent/tck/DelayQueueTest.java b/jdk/test/java/util/concurrent/tck/DelayQueueTest.java new file mode 100644 index 00000000000..4d05c864554 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/DelayQueueTest.java @@ -0,0 +1,820 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Delayed; +import java.util.concurrent.DelayQueue; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; + +import junit.framework.Test; + +public class DelayQueueTest extends JSR166TestCase { + + public static class Generic extends BlockingQueueTest { + protected BlockingQueue emptyCollection() { + return new DelayQueue(); + } + protected PDelay makeElement(int i) { + return new PDelay(i); + } + } + + public static void main(String[] args) { + main(suite(), args); + } + + public static Test suite() { + return newTestSuite(DelayQueueTest.class, + new Generic().testSuite()); + } + + /** + * A delayed implementation for testing. + * Most tests use Pseudodelays, where delays are all elapsed + * (so, no blocking solely for delays) but are still ordered + */ + static class PDelay implements Delayed { + int pseudodelay; + PDelay(int i) { pseudodelay = i; } + public int compareTo(PDelay other) { + int a = this.pseudodelay; + int b = other.pseudodelay; + return (a < b) ? -1 : (a > b) ? 1 : 0; + } + public int compareTo(Delayed y) { + return compareTo((PDelay)y); + } + public boolean equals(Object other) { + return (other instanceof PDelay) && + this.pseudodelay == ((PDelay)other).pseudodelay; + } + // suppress [overrides] javac warning + public int hashCode() { return pseudodelay; } + public long getDelay(TimeUnit ignore) { + return Integer.MIN_VALUE + pseudodelay; + } + public String toString() { + return String.valueOf(pseudodelay); + } + } + + /** + * Delayed implementation that actually delays + */ + static class NanoDelay implements Delayed { + long trigger; + NanoDelay(long i) { + trigger = System.nanoTime() + i; + } + public int compareTo(NanoDelay y) { + long i = trigger; + long j = y.trigger; + if (i < j) return -1; + if (i > j) return 1; + return 0; + } + + public int compareTo(Delayed y) { + return compareTo((NanoDelay)y); + } + + public boolean equals(Object other) { + return equals((NanoDelay)other); + } + public boolean equals(NanoDelay other) { + return other.trigger == trigger; + } + + // suppress [overrides] javac warning + public int hashCode() { return (int) trigger; } + + public long getDelay(TimeUnit unit) { + long n = trigger - System.nanoTime(); + return unit.convert(n, TimeUnit.NANOSECONDS); + } + + public long getTriggerTime() { + return trigger; + } + + public String toString() { + return String.valueOf(trigger); + } + } + + /** + * Returns a new queue of given size containing consecutive + * PDelays 0 ... n. + */ + private DelayQueue populatedQueue(int n) { + DelayQueue q = new DelayQueue(); + assertTrue(q.isEmpty()); + for (int i = n - 1; i >= 0; i -= 2) + assertTrue(q.offer(new PDelay(i))); + for (int i = (n & 1); i < n; i += 2) + assertTrue(q.offer(new PDelay(i))); + assertFalse(q.isEmpty()); + assertEquals(Integer.MAX_VALUE, q.remainingCapacity()); + assertEquals(n, q.size()); + return q; + } + + /** + * A new queue has unbounded capacity + */ + public void testConstructor1() { + assertEquals(Integer.MAX_VALUE, new DelayQueue().remainingCapacity()); + } + + /** + * Initializing from null Collection throws NPE + */ + public void testConstructor3() { + try { + new DelayQueue(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing from Collection of null elements throws NPE + */ + public void testConstructor4() { + try { + new DelayQueue(Arrays.asList(new PDelay[SIZE])); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing from Collection with some null elements throws NPE + */ + public void testConstructor5() { + PDelay[] a = new PDelay[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + a[i] = new PDelay(i); + try { + new DelayQueue(Arrays.asList(a)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Queue contains all elements of collection used to initialize + */ + public void testConstructor6() { + PDelay[] ints = new PDelay[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new PDelay(i); + DelayQueue q = new DelayQueue(Arrays.asList(ints)); + for (int i = 0; i < SIZE; ++i) + assertEquals(ints[i], q.poll()); + } + + /** + * isEmpty is true before add, false after + */ + public void testEmpty() { + DelayQueue q = new DelayQueue(); + assertTrue(q.isEmpty()); + assertEquals(Integer.MAX_VALUE, q.remainingCapacity()); + q.add(new PDelay(1)); + assertFalse(q.isEmpty()); + q.add(new PDelay(2)); + q.remove(); + q.remove(); + assertTrue(q.isEmpty()); + } + + /** + * remainingCapacity() always returns Integer.MAX_VALUE + */ + public void testRemainingCapacity() { + BlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(Integer.MAX_VALUE, q.remainingCapacity()); + assertEquals(SIZE - i, q.size()); + assertTrue(q.remove() instanceof PDelay); + } + for (int i = 0; i < SIZE; ++i) { + assertEquals(Integer.MAX_VALUE, q.remainingCapacity()); + assertEquals(i, q.size()); + assertTrue(q.add(new PDelay(i))); + } + } + + /** + * offer non-null succeeds + */ + public void testOffer() { + DelayQueue q = new DelayQueue(); + assertTrue(q.offer(new PDelay(0))); + assertTrue(q.offer(new PDelay(1))); + } + + /** + * add succeeds + */ + public void testAdd() { + DelayQueue q = new DelayQueue(); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.size()); + assertTrue(q.add(new PDelay(i))); + } + } + + /** + * addAll(this) throws IAE + */ + public void testAddAllSelf() { + DelayQueue q = populatedQueue(SIZE); + try { + q.addAll(q); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * addAll of a collection with any null elements throws NPE after + * possibly adding some elements + */ + public void testAddAll3() { + DelayQueue q = new DelayQueue(); + PDelay[] a = new PDelay[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + a[i] = new PDelay(i); + try { + q.addAll(Arrays.asList(a)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Queue contains all elements of successful addAll + */ + public void testAddAll5() { + PDelay[] empty = new PDelay[0]; + PDelay[] ints = new PDelay[SIZE]; + for (int i = SIZE - 1; i >= 0; --i) + ints[i] = new PDelay(i); + DelayQueue q = new DelayQueue(); + assertFalse(q.addAll(Arrays.asList(empty))); + assertTrue(q.addAll(Arrays.asList(ints))); + for (int i = 0; i < SIZE; ++i) + assertEquals(ints[i], q.poll()); + } + + /** + * all elements successfully put are contained + */ + public void testPut() { + DelayQueue q = new DelayQueue(); + for (int i = 0; i < SIZE; ++i) { + PDelay x = new PDelay(i); + q.put(x); + assertTrue(q.contains(x)); + } + assertEquals(SIZE, q.size()); + } + + /** + * put doesn't block waiting for take + */ + public void testPutWithTake() throws InterruptedException { + final DelayQueue q = new DelayQueue(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + q.put(new PDelay(0)); + q.put(new PDelay(0)); + q.put(new PDelay(0)); + q.put(new PDelay(0)); + }}); + + awaitTermination(t); + assertEquals(4, q.size()); + } + + /** + * timed offer does not time out + */ + public void testTimedOffer() throws InterruptedException { + final DelayQueue q = new DelayQueue(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + q.put(new PDelay(0)); + q.put(new PDelay(0)); + assertTrue(q.offer(new PDelay(0), SHORT_DELAY_MS, MILLISECONDS)); + assertTrue(q.offer(new PDelay(0), LONG_DELAY_MS, MILLISECONDS)); + }}); + + awaitTermination(t); + } + + /** + * take retrieves elements in priority order + */ + public void testTake() throws InterruptedException { + DelayQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(new PDelay(i), q.take()); + } + } + + /** + * Take removes existing elements until empty, then blocks interruptibly + */ + public void testBlockingTake() throws InterruptedException { + final DelayQueue q = populatedQueue(SIZE); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + for (int i = 0; i < SIZE; ++i) { + assertEquals(new PDelay(i), ((PDelay)q.take())); + } + + Thread.currentThread().interrupt(); + try { + q.take(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + q.take(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * poll succeeds unless empty + */ + public void testPoll() { + DelayQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(new PDelay(i), q.poll()); + } + assertNull(q.poll()); + } + + /** + * timed poll with zero timeout succeeds when non-empty, else times out + */ + public void testTimedPoll0() throws InterruptedException { + DelayQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(new PDelay(i), q.poll(0, MILLISECONDS)); + } + assertNull(q.poll(0, MILLISECONDS)); + } + + /** + * timed poll with nonzero timeout succeeds when non-empty, else times out + */ + public void testTimedPoll() throws InterruptedException { + DelayQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + long startTime = System.nanoTime(); + assertEquals(new PDelay(i), q.poll(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + long startTime = System.nanoTime(); + assertNull(q.poll(timeoutMillis(), MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + checkEmpty(q); + } + + /** + * Interrupted timed poll throws InterruptedException instead of + * returning timeout status + */ + public void testInterruptedTimedPoll() throws InterruptedException { + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + final DelayQueue q = populatedQueue(SIZE); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + for (int i = 0; i < SIZE; ++i) { + assertEquals(new PDelay(i), + ((PDelay)q.poll(LONG_DELAY_MS, MILLISECONDS))); + } + + Thread.currentThread().interrupt(); + try { + q.poll(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + q.poll(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + checkEmpty(q); + } + + /** + * peek returns next element, or null if empty + */ + public void testPeek() { + DelayQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(new PDelay(i), q.peek()); + assertEquals(new PDelay(i), q.poll()); + if (q.isEmpty()) + assertNull(q.peek()); + else + assertFalse(new PDelay(i).equals(q.peek())); + } + assertNull(q.peek()); + } + + /** + * element returns next element, or throws NSEE if empty + */ + public void testElement() { + DelayQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(new PDelay(i), q.element()); + q.poll(); + } + try { + q.element(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * remove removes next element, or throws NSEE if empty + */ + public void testRemove() { + DelayQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(new PDelay(i), q.remove()); + } + try { + q.remove(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * contains(x) reports true when elements added but not yet removed + */ + public void testContains() { + DelayQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.contains(new PDelay(i))); + q.poll(); + assertFalse(q.contains(new PDelay(i))); + } + } + + /** + * clear removes all elements + */ + public void testClear() { + DelayQueue q = populatedQueue(SIZE); + q.clear(); + assertTrue(q.isEmpty()); + assertEquals(0, q.size()); + assertEquals(Integer.MAX_VALUE, q.remainingCapacity()); + PDelay x = new PDelay(1); + q.add(x); + assertFalse(q.isEmpty()); + assertTrue(q.contains(x)); + q.clear(); + assertTrue(q.isEmpty()); + } + + /** + * containsAll(c) is true when c contains a subset of elements + */ + public void testContainsAll() { + DelayQueue q = populatedQueue(SIZE); + DelayQueue p = new DelayQueue(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.containsAll(p)); + assertFalse(p.containsAll(q)); + p.add(new PDelay(i)); + } + assertTrue(p.containsAll(q)); + } + + /** + * retainAll(c) retains only those elements of c and reports true if changed + */ + public void testRetainAll() { + DelayQueue q = populatedQueue(SIZE); + DelayQueue p = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + boolean changed = q.retainAll(p); + if (i == 0) + assertFalse(changed); + else + assertTrue(changed); + + assertTrue(q.containsAll(p)); + assertEquals(SIZE - i, q.size()); + p.remove(); + } + } + + /** + * removeAll(c) removes only those elements of c and reports true if changed + */ + public void testRemoveAll() { + for (int i = 1; i < SIZE; ++i) { + DelayQueue q = populatedQueue(SIZE); + DelayQueue p = populatedQueue(i); + assertTrue(q.removeAll(p)); + assertEquals(SIZE - i, q.size()); + for (int j = 0; j < i; ++j) { + PDelay x = (PDelay)(p.remove()); + assertFalse(q.contains(x)); + } + } + } + + /** + * toArray contains all elements + */ + public void testToArray() throws InterruptedException { + DelayQueue q = populatedQueue(SIZE); + Object[] o = q.toArray(); + Arrays.sort(o); + for (int i = 0; i < o.length; i++) + assertSame(o[i], q.take()); + } + + /** + * toArray(a) contains all elements + */ + public void testToArray2() { + DelayQueue q = populatedQueue(SIZE); + PDelay[] ints = new PDelay[SIZE]; + PDelay[] array = q.toArray(ints); + assertSame(ints, array); + Arrays.sort(ints); + for (int i = 0; i < ints.length; i++) + assertSame(ints[i], q.remove()); + } + + /** + * toArray(incompatible array type) throws ArrayStoreException + */ + public void testToArray1_BadArg() { + DelayQueue q = populatedQueue(SIZE); + try { + q.toArray(new String[10]); + shouldThrow(); + } catch (ArrayStoreException success) {} + } + + /** + * iterator iterates through all elements + */ + public void testIterator() { + DelayQueue q = populatedQueue(SIZE); + int i = 0; + Iterator it = q.iterator(); + while (it.hasNext()) { + assertTrue(q.contains(it.next())); + ++i; + } + assertEquals(i, SIZE); + assertIteratorExhausted(it); + } + + /** + * iterator of empty collection has no elements + */ + public void testEmptyIterator() { + assertIteratorExhausted(new DelayQueue().iterator()); + } + + /** + * iterator.remove removes current element + */ + public void testIteratorRemove() { + final DelayQueue q = new DelayQueue(); + q.add(new PDelay(2)); + q.add(new PDelay(1)); + q.add(new PDelay(3)); + Iterator it = q.iterator(); + it.next(); + it.remove(); + it = q.iterator(); + assertEquals(new PDelay(2), it.next()); + assertEquals(new PDelay(3), it.next()); + assertFalse(it.hasNext()); + } + + /** + * toString contains toStrings of elements + */ + public void testToString() { + DelayQueue q = populatedQueue(SIZE); + String s = q.toString(); + for (Object e : q) + assertTrue(s.contains(e.toString())); + } + + /** + * timed poll transfers elements across Executor tasks + */ + public void testPollInExecutor() { + final DelayQueue q = new DelayQueue(); + final CheckedBarrier threadsStarted = new CheckedBarrier(2); + final ExecutorService executor = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(executor)) { + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertNull(q.poll()); + threadsStarted.await(); + assertNotNull(q.poll(LONG_DELAY_MS, MILLISECONDS)); + checkEmpty(q); + }}); + + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + q.put(new PDelay(1)); + }}); + } + } + + /** + * Delayed actions do not occur until their delay elapses + */ + public void testDelay() throws InterruptedException { + DelayQueue q = new DelayQueue(); + for (int i = 0; i < SIZE; ++i) + q.add(new NanoDelay(1000000L * (SIZE - i))); + + long last = 0; + for (int i = 0; i < SIZE; ++i) { + NanoDelay e = q.take(); + long tt = e.getTriggerTime(); + assertTrue(System.nanoTime() - tt >= 0); + if (i != 0) + assertTrue(tt >= last); + last = tt; + } + assertTrue(q.isEmpty()); + } + + /** + * peek of a non-empty queue returns non-null even if not expired + */ + public void testPeekDelayed() { + DelayQueue q = new DelayQueue(); + q.add(new NanoDelay(Long.MAX_VALUE)); + assertNotNull(q.peek()); + } + + /** + * poll of a non-empty queue returns null if no expired elements. + */ + public void testPollDelayed() { + DelayQueue q = new DelayQueue(); + q.add(new NanoDelay(Long.MAX_VALUE)); + assertNull(q.poll()); + } + + /** + * timed poll of a non-empty queue returns null if no expired elements. + */ + public void testTimedPollDelayed() throws InterruptedException { + DelayQueue q = new DelayQueue(); + q.add(new NanoDelay(LONG_DELAY_MS * 1000000L)); + assertNull(q.poll(timeoutMillis(), MILLISECONDS)); + } + + /** + * drainTo(c) empties queue into another collection c + */ + public void testDrainTo() { + DelayQueue q = new DelayQueue(); + PDelay[] elems = new PDelay[SIZE]; + for (int i = 0; i < SIZE; ++i) { + elems[i] = new PDelay(i); + q.add(elems[i]); + } + ArrayList l = new ArrayList(); + q.drainTo(l); + assertEquals(0, q.size()); + for (int i = 0; i < SIZE; ++i) + assertEquals(elems[i], l.get(i)); + q.add(elems[0]); + q.add(elems[1]); + assertFalse(q.isEmpty()); + assertTrue(q.contains(elems[0])); + assertTrue(q.contains(elems[1])); + l.clear(); + q.drainTo(l); + assertEquals(0, q.size()); + assertEquals(2, l.size()); + for (int i = 0; i < 2; ++i) + assertEquals(elems[i], l.get(i)); + } + + /** + * drainTo empties queue + */ + public void testDrainToWithActivePut() throws InterruptedException { + final DelayQueue q = populatedQueue(SIZE); + Thread t = new Thread(new CheckedRunnable() { + public void realRun() { + q.put(new PDelay(SIZE + 1)); + }}); + + t.start(); + ArrayList l = new ArrayList(); + q.drainTo(l); + assertTrue(l.size() >= SIZE); + t.join(); + assertTrue(q.size() + l.size() >= SIZE); + } + + /** + * drainTo(c, n) empties first min(n, size) elements of queue into c + */ + public void testDrainToN() { + for (int i = 0; i < SIZE + 2; ++i) { + DelayQueue q = populatedQueue(SIZE); + ArrayList l = new ArrayList(); + q.drainTo(l, i); + int k = (i < SIZE) ? i : SIZE; + assertEquals(SIZE - k, q.size()); + assertEquals(k, l.size()); + } + } + + /** + * remove(null), contains(null) always return false + */ + public void testNeverContainsNull() { + Collection q = populatedQueue(SIZE); + assertFalse(q.contains(null)); + assertFalse(q.remove(null)); + } +} diff --git a/jdk/test/java/util/concurrent/tck/DoubleAccumulatorTest.java b/jdk/test/java/util/concurrent/tck/DoubleAccumulatorTest.java new file mode 100644 index 00000000000..0a7fea9eb9d --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/DoubleAccumulatorTest.java @@ -0,0 +1,183 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Phaser; +import java.util.concurrent.atomic.DoubleAccumulator; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class DoubleAccumulatorTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(DoubleAccumulatorTest.class); + } + + /** + * default constructed initializes to zero + */ + public void testConstructor() { + DoubleAccumulator ai = new DoubleAccumulator(Double::max, 0.0); + assertEquals(0.0, ai.get()); + } + + /** + * accumulate accumulates given value to current, and get returns current value + */ + public void testAccumulateAndGet() { + DoubleAccumulator ai = new DoubleAccumulator(Double::max, 0.0); + ai.accumulate(2.0); + assertEquals(2.0, ai.get()); + ai.accumulate(-4.0); + assertEquals(2.0, ai.get()); + ai.accumulate(4.0); + assertEquals(4.0, ai.get()); + } + + /** + * reset() causes subsequent get() to return zero + */ + public void testReset() { + DoubleAccumulator ai = new DoubleAccumulator(Double::max, 0.0); + ai.accumulate(2.0); + assertEquals(2.0, ai.get()); + ai.reset(); + assertEquals(0.0, ai.get()); + } + + /** + * getThenReset() returns current value; subsequent get() returns zero + */ + public void testGetThenReset() { + DoubleAccumulator ai = new DoubleAccumulator(Double::max, 0.0); + ai.accumulate(2.0); + assertEquals(2.0, ai.get()); + assertEquals(2.0, ai.getThenReset()); + assertEquals(0.0, ai.get()); + } + + /** + * toString returns current value. + */ + public void testToString() { + DoubleAccumulator ai = new DoubleAccumulator(Double::max, 0.0); + assertEquals("0.0", ai.toString()); + ai.accumulate(1.0); + assertEquals(Double.toString(1.0), ai.toString()); + } + + /** + * intValue returns current value. + */ + public void testIntValue() { + DoubleAccumulator ai = new DoubleAccumulator(Double::max, 0.0); + assertEquals(0, ai.intValue()); + ai.accumulate(1.0); + assertEquals(1, ai.intValue()); + } + + /** + * longValue returns current value. + */ + public void testLongValue() { + DoubleAccumulator ai = new DoubleAccumulator(Double::max, 0.0); + assertEquals(0, ai.longValue()); + ai.accumulate(1.0); + assertEquals(1, ai.longValue()); + } + + /** + * floatValue returns current value. + */ + public void testFloatValue() { + DoubleAccumulator ai = new DoubleAccumulator(Double::max, 0.0); + assertEquals(0.0f, ai.floatValue()); + ai.accumulate(1.0); + assertEquals(1.0f, ai.floatValue()); + } + + /** + * doubleValue returns current value. + */ + public void testDoubleValue() { + DoubleAccumulator ai = new DoubleAccumulator(Double::max, 0.0); + assertEquals(0.0, ai.doubleValue()); + ai.accumulate(1.0); + assertEquals(1.0, ai.doubleValue()); + } + + /** + * accumulates by multiple threads produce correct result + */ + public void testAccumulateAndGetMT() { + final int incs = 1000000; + final int nthreads = 4; + final ExecutorService pool = Executors.newCachedThreadPool(); + DoubleAccumulator a = new DoubleAccumulator(Double::max, 0.0); + Phaser phaser = new Phaser(nthreads + 1); + for (int i = 0; i < nthreads; ++i) + pool.execute(new AccTask(a, phaser, incs)); + phaser.arriveAndAwaitAdvance(); + phaser.arriveAndAwaitAdvance(); + double expected = incs - 1; + double result = a.get(); + assertEquals(expected, result); + pool.shutdown(); + } + + static final class AccTask implements Runnable { + final DoubleAccumulator acc; + final Phaser phaser; + final int incs; + volatile double result; + AccTask(DoubleAccumulator acc, Phaser phaser, int incs) { + this.acc = acc; + this.phaser = phaser; + this.incs = incs; + } + + public void run() { + phaser.arriveAndAwaitAdvance(); + DoubleAccumulator a = acc; + for (int i = 0; i < incs; ++i) + a.accumulate(i); + result = a.get(); + phaser.arrive(); + } + } + +} diff --git a/jdk/test/java/util/concurrent/tck/DoubleAdderTest.java b/jdk/test/java/util/concurrent/tck/DoubleAdderTest.java new file mode 100644 index 00000000000..fc7d1f47834 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/DoubleAdderTest.java @@ -0,0 +1,197 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.DoubleAdder; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class DoubleAdderTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(DoubleAdderTest.class); + } + + /** + * default constructed initializes to zero + */ + public void testConstructor() { + DoubleAdder ai = new DoubleAdder(); + assertEquals(0.0, ai.sum()); + } + + /** + * add adds given value to current, and sum returns current value + */ + public void testAddAndSum() { + DoubleAdder ai = new DoubleAdder(); + ai.add(2.0); + assertEquals(2.0, ai.sum()); + ai.add(-4.0); + assertEquals(-2.0, ai.sum()); + } + + /** + * reset() causes subsequent sum() to return zero + */ + public void testReset() { + DoubleAdder ai = new DoubleAdder(); + ai.add(2.0); + assertEquals(2.0, ai.sum()); + ai.reset(); + assertEquals(0.0, ai.sum()); + } + + /** + * sumThenReset() returns sum; subsequent sum() returns zero + */ + public void testSumThenReset() { + DoubleAdder ai = new DoubleAdder(); + ai.add(2.0); + assertEquals(2.0, ai.sum()); + assertEquals(2.0, ai.sumThenReset()); + assertEquals(0.0, ai.sum()); + } + + /** + * a deserialized serialized adder holds same value + */ + public void testSerialization() throws Exception { + DoubleAdder x = new DoubleAdder(); + DoubleAdder y = serialClone(x); + assertNotSame(x, y); + x.add(-22.0); + DoubleAdder z = serialClone(x); + assertEquals(-22.0, x.sum()); + assertEquals(0.0, y.sum()); + assertEquals(-22.0, z.sum()); + } + + /** + * toString returns current value. + */ + public void testToString() { + DoubleAdder ai = new DoubleAdder(); + assertEquals(Double.toString(0.0), ai.toString()); + ai.add(1.0); + assertEquals(Double.toString(1.0), ai.toString()); + } + + /** + * intValue returns current value. + */ + public void testIntValue() { + DoubleAdder ai = new DoubleAdder(); + assertEquals(0, ai.intValue()); + ai.add(1.0); + assertEquals(1, ai.intValue()); + } + + /** + * longValue returns current value. + */ + public void testLongValue() { + DoubleAdder ai = new DoubleAdder(); + assertEquals(0, ai.longValue()); + ai.add(1.0); + assertEquals(1, ai.longValue()); + } + + /** + * floatValue returns current value. + */ + public void testFloatValue() { + DoubleAdder ai = new DoubleAdder(); + assertEquals(0.0f, ai.floatValue()); + ai.add(1.0); + assertEquals(1.0f, ai.floatValue()); + } + + /** + * doubleValue returns current value. + */ + public void testDoubleValue() { + DoubleAdder ai = new DoubleAdder(); + assertEquals(0.0, ai.doubleValue()); + ai.add(1.0); + assertEquals(1.0, ai.doubleValue()); + } + + /** + * adds by multiple threads produce correct sum + */ + public void testAddAndSumMT() throws Throwable { + final int incs = 1000000; + final int nthreads = 4; + final ExecutorService pool = Executors.newCachedThreadPool(); + DoubleAdder a = new DoubleAdder(); + CyclicBarrier barrier = new CyclicBarrier(nthreads + 1); + for (int i = 0; i < nthreads; ++i) + pool.execute(new AdderTask(a, barrier, incs)); + barrier.await(); + barrier.await(); + double total = (long)nthreads * incs; + double sum = a.sum(); + assertEquals(sum, total); + pool.shutdown(); + } + + static final class AdderTask implements Runnable { + final DoubleAdder adder; + final CyclicBarrier barrier; + final int incs; + volatile double result; + AdderTask(DoubleAdder adder, CyclicBarrier barrier, int incs) { + this.adder = adder; + this.barrier = barrier; + this.incs = incs; + } + + public void run() { + try { + barrier.await(); + DoubleAdder a = adder; + for (int i = 0; i < incs; ++i) + a.add(1.0); + result = a.sum(); + barrier.await(); + } catch (Throwable t) { throw new Error(t); } + } + } + +} diff --git a/jdk/test/java/util/concurrent/tck/EntryTest.java b/jdk/test/java/util/concurrent/tck/EntryTest.java new file mode 100644 index 00000000000..dcd696d8d12 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/EntryTest.java @@ -0,0 +1,157 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import java.util.AbstractMap; +import java.util.Map; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class EntryTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(EntryTest.class); + } + + static final String k1 = "1"; + static final String v1 = "a"; + static final String k2 = "2"; + static final String v2 = "b"; + + /** + * A new SimpleEntry(k, v) holds k, v. + */ + public void testConstructor1() { + Map.Entry e = new AbstractMap.SimpleEntry(k1, v1); + assertEquals(k1, e.getKey()); + assertEquals(v1, e.getValue()); + } + + /** + * A new SimpleImmutableEntry(k, v) holds k, v. + */ + public void testConstructor2() { + Map.Entry s = new AbstractMap.SimpleImmutableEntry(k1, v1); + assertEquals(k1, s.getKey()); + assertEquals(v1, s.getValue()); + } + + /** + * A new SimpleEntry(entry(k, v)) holds k, v. + */ + public void testConstructor3() { + Map.Entry e2 = new AbstractMap.SimpleEntry(k1, v1); + Map.Entry e = new AbstractMap.SimpleEntry(e2); + assertEquals(k1, e.getKey()); + assertEquals(v1, e.getValue()); + } + + /** + * A new SimpleImmutableEntry(entry(k, v)) holds k, v. + */ + public void testConstructor4() { + Map.Entry s2 = new AbstractMap.SimpleImmutableEntry(k1, v1); + Map.Entry s = new AbstractMap.SimpleImmutableEntry(s2); + assertEquals(k1, s.getKey()); + assertEquals(v1, s.getValue()); + } + + /** + * Entries with same key-value pairs are equal and have same + * hashcodes + */ + public void testEquals() { + Map.Entry e2 = new AbstractMap.SimpleEntry(k1, v1); + Map.Entry e = new AbstractMap.SimpleEntry(e2); + Map.Entry s2 = new AbstractMap.SimpleImmutableEntry(k1, v1); + Map.Entry s = new AbstractMap.SimpleImmutableEntry(s2); + assertEquals(e2, e); + assertEquals(e2.hashCode(), e.hashCode()); + assertEquals(s2, s); + assertEquals(s2.hashCode(), s.hashCode()); + assertEquals(e2, s2); + assertEquals(e2.hashCode(), s2.hashCode()); + assertEquals(e, s); + assertEquals(e.hashCode(), s.hashCode()); + } + + /** + * Entries with different key-value pairs are not equal + */ + public void testNotEquals() { + Map.Entry e2 = new AbstractMap.SimpleEntry(k1, v1); + Map.Entry e = new AbstractMap.SimpleEntry(k2, v1); + assertFalse(e2.equals(e)); + e = new AbstractMap.SimpleEntry(k1, v2); + assertFalse(e2.equals(e)); + e = new AbstractMap.SimpleEntry(k2, v2); + assertFalse(e2.equals(e)); + + Map.Entry s2 = new AbstractMap.SimpleImmutableEntry(k1, v1); + Map.Entry s = new AbstractMap.SimpleImmutableEntry(k2, v1); + assertFalse(s2.equals(s)); + s = new AbstractMap.SimpleImmutableEntry(k1, v2); + assertFalse(s2.equals(s)); + s = new AbstractMap.SimpleImmutableEntry(k2, v2); + assertFalse(s2.equals(s)); + } + + /** + * getValue returns last setValue for SimpleEntry + */ + public void testSetValue1() { + Map.Entry e2 = new AbstractMap.SimpleEntry(k1, v1); + Map.Entry e = new AbstractMap.SimpleEntry(e2); + assertEquals(k1, e.getKey()); + assertEquals(v1, e.getValue()); + e.setValue(k2); + assertEquals(k2, e.getValue()); + assertFalse(e2.equals(e)); + } + + /** + * setValue for SimpleImmutableEntry throws UnsupportedOperationException + */ + public void testSetValue2() { + Map.Entry s2 = new AbstractMap.SimpleImmutableEntry(k1, v1); + Map.Entry s = new AbstractMap.SimpleImmutableEntry(s2); + assertEquals(k1, s.getKey()); + assertEquals(v1, s.getValue()); + try { + s.setValue(k2); + shouldThrow(); + } catch (UnsupportedOperationException success) {} + } +} diff --git a/jdk/test/java/util/concurrent/tck/ExchangerTest.java b/jdk/test/java/util/concurrent/tck/ExchangerTest.java new file mode 100644 index 00000000000..ba6c5443623 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ExchangerTest.java @@ -0,0 +1,180 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Exchanger; +import java.util.concurrent.TimeoutException; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ExchangerTest extends JSR166TestCase { + + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(ExchangerTest.class); + } + + /** + * exchange exchanges objects across two threads + */ + public void testExchange() { + final Exchanger e = new Exchanger(); + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertSame(one, e.exchange(two)); + assertSame(two, e.exchange(one)); + }}); + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertSame(two, e.exchange(one)); + assertSame(one, e.exchange(two)); + }}); + + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * timed exchange exchanges objects across two threads + */ + public void testTimedExchange() { + final Exchanger e = new Exchanger(); + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() throws Exception { + assertSame(one, e.exchange(two, LONG_DELAY_MS, MILLISECONDS)); + assertSame(two, e.exchange(one, LONG_DELAY_MS, MILLISECONDS)); + }}); + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() throws Exception { + assertSame(two, e.exchange(one, LONG_DELAY_MS, MILLISECONDS)); + assertSame(one, e.exchange(two, LONG_DELAY_MS, MILLISECONDS)); + }}); + + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * interrupt during wait for exchange throws IE + */ + public void testExchange_InterruptedException() { + final Exchanger e = new Exchanger(); + final CountDownLatch threadStarted = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + e.exchange(one); + }}); + + await(threadStarted); + t.interrupt(); + awaitTermination(t); + } + + /** + * interrupt during wait for timed exchange throws IE + */ + public void testTimedExchange_InterruptedException() { + final Exchanger e = new Exchanger(); + final CountDownLatch threadStarted = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws Exception { + threadStarted.countDown(); + e.exchange(null, LONG_DELAY_MS, MILLISECONDS); + }}); + + await(threadStarted); + t.interrupt(); + awaitTermination(t); + } + + /** + * timeout during wait for timed exchange throws TimeoutException + */ + public void testExchange_TimeoutException() { + final Exchanger e = new Exchanger(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws Exception { + long startTime = System.nanoTime(); + try { + e.exchange(null, timeoutMillis(), MILLISECONDS); + shouldThrow(); + } catch (TimeoutException success) {} + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + }}); + + awaitTermination(t); + } + + /** + * If one exchanging thread is interrupted, another succeeds. + */ + public void testReplacementAfterExchange() { + final Exchanger e = new Exchanger(); + final CountDownLatch exchanged = new CountDownLatch(2); + final CountDownLatch interrupted = new CountDownLatch(1); + Thread t1 = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + assertSame(two, e.exchange(one)); + exchanged.countDown(); + e.exchange(two); + }}); + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertSame(one, e.exchange(two)); + exchanged.countDown(); + interrupted.await(); + assertSame(three, e.exchange(one)); + }}); + Thread t3 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + interrupted.await(); + assertSame(one, e.exchange(three)); + }}); + + await(exchanged); + t1.interrupt(); + awaitTermination(t1); + interrupted.countDown(); + awaitTermination(t2); + awaitTermination(t3); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/ExecutorCompletionServiceTest.java b/jdk/test/java/util/concurrent/tck/ExecutorCompletionServiceTest.java new file mode 100644 index 00000000000..29db17a7a0d --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ExecutorCompletionServiceTest.java @@ -0,0 +1,241 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.RunnableFuture; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ExecutorCompletionServiceTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(ExecutorCompletionServiceTest.class); + } + + /** + * Creating a new ECS with null Executor throw NPE + */ + public void testConstructorNPE() { + try { + new ExecutorCompletionService(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Creating a new ECS with null queue throw NPE + */ + public void testConstructorNPE2() { + try { + ExecutorService e = Executors.newCachedThreadPool(); + new ExecutorCompletionService(e, null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Submitting a null callable throws NPE + */ + public void testSubmitNPE() { + final ExecutorService e = Executors.newCachedThreadPool(); + final ExecutorCompletionService ecs = new ExecutorCompletionService(e); + try (PoolCleaner cleaner = cleaner(e)) { + Callable c = null; + try { + ecs.submit(c); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * Submitting a null runnable throws NPE + */ + public void testSubmitNPE2() { + final ExecutorService e = Executors.newCachedThreadPool(); + final ExecutorCompletionService ecs = new ExecutorCompletionService(e); + try (PoolCleaner cleaner = cleaner(e)) { + Runnable r = null; + try { + ecs.submit(r, Boolean.TRUE); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * A taken submitted task is completed + */ + public void testTake() throws InterruptedException { + final ExecutorService e = Executors.newCachedThreadPool(); + final ExecutorCompletionService ecs = new ExecutorCompletionService(e); + try (PoolCleaner cleaner = cleaner(e)) { + Callable c = new StringTask(); + ecs.submit(c); + Future f = ecs.take(); + assertTrue(f.isDone()); + } + } + + /** + * Take returns the same future object returned by submit + */ + public void testTake2() throws InterruptedException { + final ExecutorService e = Executors.newCachedThreadPool(); + final ExecutorCompletionService ecs = new ExecutorCompletionService(e); + try (PoolCleaner cleaner = cleaner(e)) { + Callable c = new StringTask(); + Future f1 = ecs.submit(c); + Future f2 = ecs.take(); + assertSame(f1, f2); + } + } + + /** + * If poll returns non-null, the returned task is completed + */ + public void testPoll1() throws Exception { + final ExecutorService e = Executors.newCachedThreadPool(); + final ExecutorCompletionService ecs = new ExecutorCompletionService(e); + try (PoolCleaner cleaner = cleaner(e)) { + assertNull(ecs.poll()); + Callable c = new StringTask(); + ecs.submit(c); + + long startTime = System.nanoTime(); + Future f; + while ((f = ecs.poll()) == null) { + if (millisElapsedSince(startTime) > LONG_DELAY_MS) + fail("timed out"); + Thread.yield(); + } + assertTrue(f.isDone()); + assertSame(TEST_STRING, f.get()); + } + } + + /** + * If timed poll returns non-null, the returned task is completed + */ + public void testPoll2() throws InterruptedException { + final ExecutorService e = Executors.newCachedThreadPool(); + final ExecutorCompletionService ecs = new ExecutorCompletionService(e); + try (PoolCleaner cleaner = cleaner(e)) { + assertNull(ecs.poll()); + Callable c = new StringTask(); + ecs.submit(c); + Future f = ecs.poll(SHORT_DELAY_MS, MILLISECONDS); + if (f != null) + assertTrue(f.isDone()); + } + } + + /** + * Submitting to underlying AES that overrides newTaskFor(Callable) + * returns and eventually runs Future returned by newTaskFor. + */ + public void testNewTaskForCallable() throws InterruptedException { + final AtomicBoolean done = new AtomicBoolean(false); + class MyCallableFuture extends FutureTask { + MyCallableFuture(Callable c) { super(c); } + protected void done() { done.set(true); } + } + final ExecutorService e = + new ThreadPoolExecutor(1, 1, + 30L, TimeUnit.SECONDS, + new ArrayBlockingQueue(1)) { + protected RunnableFuture newTaskFor(Callable c) { + return new MyCallableFuture(c); + }}; + ExecutorCompletionService ecs = + new ExecutorCompletionService(e); + try (PoolCleaner cleaner = cleaner(e)) { + assertNull(ecs.poll()); + Callable c = new StringTask(); + Future f1 = ecs.submit(c); + assertTrue("submit must return MyCallableFuture", + f1 instanceof MyCallableFuture); + Future f2 = ecs.take(); + assertSame("submit and take must return same objects", f1, f2); + assertTrue("completed task must have set done", done.get()); + } + } + + /** + * Submitting to underlying AES that overrides newTaskFor(Runnable,T) + * returns and eventually runs Future returned by newTaskFor. + */ + public void testNewTaskForRunnable() throws InterruptedException { + final AtomicBoolean done = new AtomicBoolean(false); + class MyRunnableFuture extends FutureTask { + MyRunnableFuture(Runnable t, V r) { super(t, r); } + protected void done() { done.set(true); } + } + final ExecutorService e = + new ThreadPoolExecutor(1, 1, + 30L, TimeUnit.SECONDS, + new ArrayBlockingQueue(1)) { + protected RunnableFuture newTaskFor(Runnable t, T r) { + return new MyRunnableFuture(t, r); + }}; + final ExecutorCompletionService ecs = + new ExecutorCompletionService(e); + try (PoolCleaner cleaner = cleaner(e)) { + assertNull(ecs.poll()); + Runnable r = new NoOpRunnable(); + Future f1 = ecs.submit(r, null); + assertTrue("submit must return MyRunnableFuture", + f1 instanceof MyRunnableFuture); + Future f2 = ecs.take(); + assertSame("submit and take must return same objects", f1, f2); + assertTrue("completed task must have set done", done.get()); + } + } + +} diff --git a/jdk/test/java/util/concurrent/tck/ExecutorsTest.java b/jdk/test/java/util/concurrent/tck/ExecutorsTest.java new file mode 100644 index 00000000000..ec17b54b507 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ExecutorsTest.java @@ -0,0 +1,623 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.security.AccessControlContext; +import java.security.AccessControlException; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadPoolExecutor; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ExecutorsTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(ExecutorsTest.class); + } + + /** + * A newCachedThreadPool can execute runnables + */ + public void testNewCachedThreadPool1() { + final ExecutorService e = Executors.newCachedThreadPool(); + try (PoolCleaner cleaner = cleaner(e)) { + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + } + } + + /** + * A newCachedThreadPool with given ThreadFactory can execute runnables + */ + public void testNewCachedThreadPool2() { + final ExecutorService e = Executors.newCachedThreadPool(new SimpleThreadFactory()); + try (PoolCleaner cleaner = cleaner(e)) { + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + } + } + + /** + * A newCachedThreadPool with null ThreadFactory throws NPE + */ + public void testNewCachedThreadPool3() { + try { + ExecutorService e = Executors.newCachedThreadPool(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * A new SingleThreadExecutor can execute runnables + */ + public void testNewSingleThreadExecutor1() { + final ExecutorService e = Executors.newSingleThreadExecutor(); + try (PoolCleaner cleaner = cleaner(e)) { + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + } + } + + /** + * A new SingleThreadExecutor with given ThreadFactory can execute runnables + */ + public void testNewSingleThreadExecutor2() { + final ExecutorService e = Executors.newSingleThreadExecutor(new SimpleThreadFactory()); + try (PoolCleaner cleaner = cleaner(e)) { + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + } + } + + /** + * A new SingleThreadExecutor with null ThreadFactory throws NPE + */ + public void testNewSingleThreadExecutor3() { + try { + ExecutorService e = Executors.newSingleThreadExecutor(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * A new SingleThreadExecutor cannot be casted to concrete implementation + */ + public void testCastNewSingleThreadExecutor() { + final ExecutorService e = Executors.newSingleThreadExecutor(); + try (PoolCleaner cleaner = cleaner(e)) { + try { + ThreadPoolExecutor tpe = (ThreadPoolExecutor)e; + shouldThrow(); + } catch (ClassCastException success) {} + } + } + + /** + * A new newFixedThreadPool can execute runnables + */ + public void testNewFixedThreadPool1() { + final ExecutorService e = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(e)) { + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + } + } + + /** + * A new newFixedThreadPool with given ThreadFactory can execute runnables + */ + public void testNewFixedThreadPool2() { + final ExecutorService e = Executors.newFixedThreadPool(2, new SimpleThreadFactory()); + try (PoolCleaner cleaner = cleaner(e)) { + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + } + } + + /** + * A new newFixedThreadPool with null ThreadFactory throws NPE + */ + public void testNewFixedThreadPool3() { + try { + ExecutorService e = Executors.newFixedThreadPool(2, null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * A new newFixedThreadPool with 0 threads throws IAE + */ + public void testNewFixedThreadPool4() { + try { + ExecutorService e = Executors.newFixedThreadPool(0); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * An unconfigurable newFixedThreadPool can execute runnables + */ + public void testUnconfigurableExecutorService() { + final ExecutorService e = Executors.unconfigurableExecutorService(Executors.newFixedThreadPool(2)); + try (PoolCleaner cleaner = cleaner(e)) { + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + e.execute(new NoOpRunnable()); + } + } + + /** + * unconfigurableExecutorService(null) throws NPE + */ + public void testUnconfigurableExecutorServiceNPE() { + try { + ExecutorService e = Executors.unconfigurableExecutorService(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * unconfigurableScheduledExecutorService(null) throws NPE + */ + public void testUnconfigurableScheduledExecutorServiceNPE() { + try { + ExecutorService e = Executors.unconfigurableScheduledExecutorService(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * a newSingleThreadScheduledExecutor successfully runs delayed task + */ + public void testNewSingleThreadScheduledExecutor() throws Exception { + final ScheduledExecutorService p = Executors.newSingleThreadScheduledExecutor(); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch proceed = new CountDownLatch(1); + final Runnable task = new CheckedRunnable() { + public void realRun() { + await(proceed); + }}; + long startTime = System.nanoTime(); + Future f = p.schedule(Executors.callable(task, Boolean.TRUE), + timeoutMillis(), MILLISECONDS); + assertFalse(f.isDone()); + proceed.countDown(); + assertSame(Boolean.TRUE, f.get(LONG_DELAY_MS, MILLISECONDS)); + assertSame(Boolean.TRUE, f.get()); + assertTrue(f.isDone()); + assertFalse(f.isCancelled()); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + } + } + + /** + * a newScheduledThreadPool successfully runs delayed task + */ + public void testNewScheduledThreadPool() throws Exception { + final ScheduledExecutorService p = Executors.newScheduledThreadPool(2); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch proceed = new CountDownLatch(1); + final Runnable task = new CheckedRunnable() { + public void realRun() { + await(proceed); + }}; + long startTime = System.nanoTime(); + Future f = p.schedule(Executors.callable(task, Boolean.TRUE), + timeoutMillis(), MILLISECONDS); + assertFalse(f.isDone()); + proceed.countDown(); + assertSame(Boolean.TRUE, f.get(LONG_DELAY_MS, MILLISECONDS)); + assertSame(Boolean.TRUE, f.get()); + assertTrue(f.isDone()); + assertFalse(f.isCancelled()); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + } + } + + /** + * an unconfigurable newScheduledThreadPool successfully runs delayed task + */ + public void testUnconfigurableScheduledExecutorService() throws Exception { + final ScheduledExecutorService p = + Executors.unconfigurableScheduledExecutorService + (Executors.newScheduledThreadPool(2)); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch proceed = new CountDownLatch(1); + final Runnable task = new CheckedRunnable() { + public void realRun() { + await(proceed); + }}; + long startTime = System.nanoTime(); + Future f = p.schedule(Executors.callable(task, Boolean.TRUE), + timeoutMillis(), MILLISECONDS); + assertFalse(f.isDone()); + proceed.countDown(); + assertSame(Boolean.TRUE, f.get(LONG_DELAY_MS, MILLISECONDS)); + assertSame(Boolean.TRUE, f.get()); + assertTrue(f.isDone()); + assertFalse(f.isCancelled()); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + } + } + + /** + * Future.get on submitted tasks will time out if they compute too long. + */ + public void testTimedCallable() throws Exception { + final ExecutorService[] executors = { + Executors.newSingleThreadExecutor(), + Executors.newCachedThreadPool(), + Executors.newFixedThreadPool(2), + Executors.newScheduledThreadPool(2), + }; + + final Runnable sleeper = new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + delay(LONG_DELAY_MS); + }}; + + List threads = new ArrayList(); + for (final ExecutorService executor : executors) { + threads.add(newStartedThread(new CheckedRunnable() { + public void realRun() { + Future future = executor.submit(sleeper); + assertFutureTimesOut(future); + }})); + } + for (Thread thread : threads) + awaitTermination(thread); + for (ExecutorService executor : executors) + joinPool(executor); + } + + /** + * ThreadPoolExecutor using defaultThreadFactory has + * specified group, priority, daemon status, and name + */ + public void testDefaultThreadFactory() throws Exception { + final ThreadGroup egroup = Thread.currentThread().getThreadGroup(); + final CountDownLatch done = new CountDownLatch(1); + Runnable r = new CheckedRunnable() { + public void realRun() { + try { + Thread current = Thread.currentThread(); + assertTrue(!current.isDaemon()); + assertTrue(current.getPriority() <= Thread.NORM_PRIORITY); + ThreadGroup g = current.getThreadGroup(); + SecurityManager s = System.getSecurityManager(); + if (s != null) + assertTrue(g == s.getThreadGroup()); + else + assertTrue(g == egroup); + String name = current.getName(); + assertTrue(name.endsWith("thread-1")); + } catch (SecurityException ok) { + // Also pass if not allowed to change setting + } + done.countDown(); + }}; + ExecutorService e = Executors.newSingleThreadExecutor(Executors.defaultThreadFactory()); + try (PoolCleaner cleaner = cleaner(e)) { + e.execute(r); + await(done); + } + } + + /** + * ThreadPoolExecutor using privilegedThreadFactory has + * specified group, priority, daemon status, name, + * access control context and context class loader + */ + public void testPrivilegedThreadFactory() throws Exception { + final CountDownLatch done = new CountDownLatch(1); + Runnable r = new CheckedRunnable() { + public void realRun() throws Exception { + final ThreadGroup egroup = Thread.currentThread().getThreadGroup(); + final ClassLoader thisccl = Thread.currentThread().getContextClassLoader(); + final AccessControlContext thisacc = AccessController.getContext(); + Runnable r = new CheckedRunnable() { + public void realRun() { + Thread current = Thread.currentThread(); + assertTrue(!current.isDaemon()); + assertTrue(current.getPriority() <= Thread.NORM_PRIORITY); + ThreadGroup g = current.getThreadGroup(); + SecurityManager s = System.getSecurityManager(); + if (s != null) + assertTrue(g == s.getThreadGroup()); + else + assertTrue(g == egroup); + String name = current.getName(); + assertTrue(name.endsWith("thread-1")); + assertSame(thisccl, current.getContextClassLoader()); + assertEquals(thisacc, AccessController.getContext()); + done.countDown(); + }}; + ExecutorService e = Executors.newSingleThreadExecutor(Executors.privilegedThreadFactory()); + try (PoolCleaner cleaner = cleaner(e)) { + e.execute(r); + await(done); + } + }}; + + runWithPermissions(r, + new RuntimePermission("getClassLoader"), + new RuntimePermission("setContextClassLoader"), + new RuntimePermission("modifyThread")); + } + + boolean haveCCLPermissions() { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + try { + sm.checkPermission(new RuntimePermission("setContextClassLoader")); + sm.checkPermission(new RuntimePermission("getClassLoader")); + } catch (AccessControlException e) { + return false; + } + } + return true; + } + + void checkCCL() { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(new RuntimePermission("setContextClassLoader")); + sm.checkPermission(new RuntimePermission("getClassLoader")); + } + } + + class CheckCCL implements Callable { + public Object call() { + checkCCL(); + return null; + } + } + + /** + * Without class loader permissions, creating + * privilegedCallableUsingCurrentClassLoader throws ACE + */ + public void testCreatePrivilegedCallableUsingCCLWithNoPrivs() { + Runnable r = new CheckedRunnable() { + public void realRun() throws Exception { + if (System.getSecurityManager() == null) + return; + try { + Executors.privilegedCallableUsingCurrentClassLoader(new NoOpCallable()); + shouldThrow(); + } catch (AccessControlException success) {} + }}; + + runWithoutPermissions(r); + } + + /** + * With class loader permissions, calling + * privilegedCallableUsingCurrentClassLoader does not throw ACE + */ + public void testPrivilegedCallableUsingCCLWithPrivs() throws Exception { + Runnable r = new CheckedRunnable() { + public void realRun() throws Exception { + Executors.privilegedCallableUsingCurrentClassLoader + (new NoOpCallable()) + .call(); + }}; + + runWithPermissions(r, + new RuntimePermission("getClassLoader"), + new RuntimePermission("setContextClassLoader")); + } + + /** + * Without permissions, calling privilegedCallable throws ACE + */ + public void testPrivilegedCallableWithNoPrivs() throws Exception { + // Avoid classloader-related SecurityExceptions in swingui.TestRunner + Executors.privilegedCallable(new CheckCCL()); + + Runnable r = new CheckedRunnable() { + public void realRun() throws Exception { + if (System.getSecurityManager() == null) + return; + Callable task = Executors.privilegedCallable(new CheckCCL()); + try { + task.call(); + shouldThrow(); + } catch (AccessControlException success) {} + }}; + + runWithoutPermissions(r); + + // It seems rather difficult to test that the + // AccessControlContext of the privilegedCallable is used + // instead of its caller. Below is a failed attempt to do + // that, which does not work because the AccessController + // cannot capture the internal state of the current Policy. + // It would be much more work to differentiate based on, + // e.g. CodeSource. + +// final AccessControlContext[] noprivAcc = new AccessControlContext[1]; +// final Callable[] task = new Callable[1]; + +// runWithPermissions +// (new CheckedRunnable() { +// public void realRun() { +// if (System.getSecurityManager() == null) +// return; +// noprivAcc[0] = AccessController.getContext(); +// task[0] = Executors.privilegedCallable(new CheckCCL()); +// try { +// AccessController.doPrivileged(new PrivilegedAction() { +// public Void run() { +// checkCCL(); +// return null; +// }}, noprivAcc[0]); +// shouldThrow(); +// } catch (AccessControlException success) {} +// }}); + +// runWithPermissions +// (new CheckedRunnable() { +// public void realRun() throws Exception { +// if (System.getSecurityManager() == null) +// return; +// // Verify that we have an underprivileged ACC +// try { +// AccessController.doPrivileged(new PrivilegedAction() { +// public Void run() { +// checkCCL(); +// return null; +// }}, noprivAcc[0]); +// shouldThrow(); +// } catch (AccessControlException success) {} + +// try { +// task[0].call(); +// shouldThrow(); +// } catch (AccessControlException success) {} +// }}, +// new RuntimePermission("getClassLoader"), +// new RuntimePermission("setContextClassLoader")); + } + + /** + * With permissions, calling privilegedCallable succeeds + */ + public void testPrivilegedCallableWithPrivs() throws Exception { + Runnable r = new CheckedRunnable() { + public void realRun() throws Exception { + Executors.privilegedCallable(new CheckCCL()).call(); + }}; + + runWithPermissions(r, + new RuntimePermission("getClassLoader"), + new RuntimePermission("setContextClassLoader")); + } + + /** + * callable(Runnable) returns null when called + */ + public void testCallable1() throws Exception { + Callable c = Executors.callable(new NoOpRunnable()); + assertNull(c.call()); + } + + /** + * callable(Runnable, result) returns result when called + */ + public void testCallable2() throws Exception { + Callable c = Executors.callable(new NoOpRunnable(), one); + assertSame(one, c.call()); + } + + /** + * callable(PrivilegedAction) returns its result when called + */ + public void testCallable3() throws Exception { + Callable c = Executors.callable(new PrivilegedAction() { + public Object run() { return one; }}); + assertSame(one, c.call()); + } + + /** + * callable(PrivilegedExceptionAction) returns its result when called + */ + public void testCallable4() throws Exception { + Callable c = Executors.callable(new PrivilegedExceptionAction() { + public Object run() { return one; }}); + assertSame(one, c.call()); + } + + /** + * callable(null Runnable) throws NPE + */ + public void testCallableNPE1() { + try { + Callable c = Executors.callable((Runnable) null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * callable(null, result) throws NPE + */ + public void testCallableNPE2() { + try { + Callable c = Executors.callable((Runnable) null, one); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * callable(null PrivilegedAction) throws NPE + */ + public void testCallableNPE3() { + try { + Callable c = Executors.callable((PrivilegedAction) null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * callable(null PrivilegedExceptionAction) throws NPE + */ + public void testCallableNPE4() { + try { + Callable c = Executors.callable((PrivilegedExceptionAction) null); + shouldThrow(); + } catch (NullPointerException success) {} + } + +} diff --git a/jdk/test/java/util/concurrent/tck/ForkJoinPool8Test.java b/jdk/test/java/util/concurrent/tck/ForkJoinPool8Test.java new file mode 100644 index 00000000000..ee991a21c80 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ForkJoinPool8Test.java @@ -0,0 +1,1615 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; + +import java.util.HashSet; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CountedCompleter; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinTask; +import java.util.concurrent.RecursiveAction; +import java.util.concurrent.TimeoutException; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ForkJoinPool8Test extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + + public static Test suite() { + return new TestSuite(ForkJoinPool8Test.class); + } + + /** + * Common pool exists and has expected parallelism. + */ + public void testCommonPoolParallelism() { + assertEquals(ForkJoinPool.getCommonPoolParallelism(), + ForkJoinPool.commonPool().getParallelism()); + } + + /** + * Common pool cannot be shut down + */ + public void testCommonPoolShutDown() { + assertFalse(ForkJoinPool.commonPool().isShutdown()); + assertFalse(ForkJoinPool.commonPool().isTerminating()); + assertFalse(ForkJoinPool.commonPool().isTerminated()); + ForkJoinPool.commonPool().shutdown(); + assertFalse(ForkJoinPool.commonPool().isShutdown()); + assertFalse(ForkJoinPool.commonPool().isTerminating()); + assertFalse(ForkJoinPool.commonPool().isTerminated()); + ForkJoinPool.commonPool().shutdownNow(); + assertFalse(ForkJoinPool.commonPool().isShutdown()); + assertFalse(ForkJoinPool.commonPool().isTerminating()); + assertFalse(ForkJoinPool.commonPool().isTerminated()); + } + + /* + * All of the following test methods are adaptations of those for + * RecursiveAction and CountedCompleter, but with all actions + * executed in the common pool, generally implicitly via + * checkInvoke. + */ + + private void checkInvoke(ForkJoinTask a) { + checkNotDone(a); + assertNull(a.invoke()); + checkCompletedNormally(a); + } + + void checkNotDone(ForkJoinTask a) { + assertFalse(a.isDone()); + assertFalse(a.isCompletedNormally()); + assertFalse(a.isCompletedAbnormally()); + assertFalse(a.isCancelled()); + assertNull(a.getException()); + assertNull(a.getRawResult()); + + if (! ForkJoinTask.inForkJoinPool()) { + Thread.currentThread().interrupt(); + try { + a.get(); + shouldThrow(); + } catch (InterruptedException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + + Thread.currentThread().interrupt(); + try { + a.get(5L, SECONDS); + shouldThrow(); + } catch (InterruptedException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + try { + a.get(0L, SECONDS); + shouldThrow(); + } catch (TimeoutException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + void checkCompletedNormally(ForkJoinTask a) { + assertTrue(a.isDone()); + assertFalse(a.isCancelled()); + assertTrue(a.isCompletedNormally()); + assertFalse(a.isCompletedAbnormally()); + assertNull(a.getException()); + assertNull(a.getRawResult()); + assertNull(a.join()); + assertFalse(a.cancel(false)); + assertFalse(a.cancel(true)); + try { + assertNull(a.get()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + try { + assertNull(a.get(5L, SECONDS)); + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + void checkCancelled(ForkJoinTask a) { + assertTrue(a.isDone()); + assertTrue(a.isCancelled()); + assertFalse(a.isCompletedNormally()); + assertTrue(a.isCompletedAbnormally()); + assertTrue(a.getException() instanceof CancellationException); + assertNull(a.getRawResult()); + + try { + a.join(); + shouldThrow(); + } catch (CancellationException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + + try { + a.get(); + shouldThrow(); + } catch (CancellationException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + + try { + a.get(5L, SECONDS); + shouldThrow(); + } catch (CancellationException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + void checkCompletedAbnormally(ForkJoinTask a, Throwable t) { + assertTrue(a.isDone()); + assertFalse(a.isCancelled()); + assertFalse(a.isCompletedNormally()); + assertTrue(a.isCompletedAbnormally()); + assertSame(t.getClass(), a.getException().getClass()); + assertNull(a.getRawResult()); + assertFalse(a.cancel(false)); + assertFalse(a.cancel(true)); + + try { + a.join(); + shouldThrow(); + } catch (Throwable expected) { + assertSame(expected.getClass(), t.getClass()); + } + + try { + a.get(); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(t.getClass(), success.getCause().getClass()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + + try { + a.get(5L, SECONDS); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(t.getClass(), success.getCause().getClass()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + public static final class FJException extends RuntimeException { + public FJException() { super(); } + public FJException(Throwable cause) { super(cause); } + } + + // A simple recursive action for testing + final class FibAction extends CheckedRecursiveAction { + final int number; + int result; + FibAction(int n) { number = n; } + protected void realCompute() { + int n = number; + if (n <= 1) + result = n; + else { + FibAction f1 = new FibAction(n - 1); + FibAction f2 = new FibAction(n - 2); + invokeAll(f1, f2); + result = f1.result + f2.result; + } + } + } + + // A recursive action failing in base case + static final class FailingFibAction extends RecursiveAction { + final int number; + int result; + FailingFibAction(int n) { number = n; } + public void compute() { + int n = number; + if (n <= 1) + throw new FJException(); + else { + FailingFibAction f1 = new FailingFibAction(n - 1); + FailingFibAction f2 = new FailingFibAction(n - 2); + invokeAll(f1, f2); + result = f1.result + f2.result; + } + } + } + + /** + * invoke returns when task completes normally. + * isCompletedAbnormally and isCancelled return false for normally + * completed tasks. getRawResult of a RecursiveAction returns null; + */ + public void testInvoke() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + assertNull(f.invoke()); + assertEquals(21, f.result); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * quietlyInvoke task returns when task completes normally. + * isCompletedAbnormally and isCancelled return false for normally + * completed tasks + */ + public void testQuietlyInvoke() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + f.quietlyInvoke(); + assertEquals(21, f.result); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * join of a forked task returns when task completes + */ + public void testForkJoin() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + assertNull(f.join()); + assertEquals(21, f.result); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * join/quietlyJoin of a forked task succeeds in the presence of interrupts + */ + public void testJoinIgnoresInterrupts() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + final Thread myself = Thread.currentThread(); + + // test join() + assertSame(f, f.fork()); + myself.interrupt(); + assertTrue(myself.isInterrupted()); + assertNull(f.join()); + Thread.interrupted(); + assertEquals(21, f.result); + checkCompletedNormally(f); + + f = new FibAction(8); + f.cancel(true); + assertSame(f, f.fork()); + myself.interrupt(); + assertTrue(myself.isInterrupted()); + try { + f.join(); + shouldThrow(); + } catch (CancellationException success) { + Thread.interrupted(); + checkCancelled(f); + } + + f = new FibAction(8); + f.completeExceptionally(new FJException()); + assertSame(f, f.fork()); + myself.interrupt(); + assertTrue(myself.isInterrupted()); + try { + f.join(); + shouldThrow(); + } catch (FJException success) { + Thread.interrupted(); + checkCompletedAbnormally(f, success); + } + + // test quietlyJoin() + f = new FibAction(8); + assertSame(f, f.fork()); + myself.interrupt(); + assertTrue(myself.isInterrupted()); + f.quietlyJoin(); + Thread.interrupted(); + assertEquals(21, f.result); + checkCompletedNormally(f); + + f = new FibAction(8); + f.cancel(true); + assertSame(f, f.fork()); + myself.interrupt(); + assertTrue(myself.isInterrupted()); + f.quietlyJoin(); + Thread.interrupted(); + checkCancelled(f); + + f = new FibAction(8); + f.completeExceptionally(new FJException()); + assertSame(f, f.fork()); + myself.interrupt(); + assertTrue(myself.isInterrupted()); + f.quietlyJoin(); + Thread.interrupted(); + checkCompletedAbnormally(f, f.getException()); + }}; + checkInvoke(a); + a.reinitialize(); + checkInvoke(a); + } + + /** + * get of a forked task returns when task completes + */ + public void testForkGet() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + assertNull(f.get()); + assertEquals(21, f.result); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * timed get of a forked task returns when task completes + */ + public void testForkTimedGet() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + assertNull(f.get(5L, SECONDS)); + assertEquals(21, f.result); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * timed get with null time unit throws NPE + */ + public void testForkTimedGetNPE() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + try { + f.get(5L, null); + shouldThrow(); + } catch (NullPointerException success) {} + }}; + checkInvoke(a); + } + + /** + * quietlyJoin of a forked task returns when task completes + */ + public void testForkQuietlyJoin() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + f.quietlyJoin(); + assertEquals(21, f.result); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * invoke task throws exception when task completes abnormally + */ + public void testAbnormalInvoke() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingFibAction f = new FailingFibAction(8); + try { + f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + checkInvoke(a); + } + + /** + * quietlyInvoke task returns when task completes abnormally + */ + public void testAbnormalQuietlyInvoke() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingFibAction f = new FailingFibAction(8); + f.quietlyInvoke(); + assertTrue(f.getException() instanceof FJException); + checkCompletedAbnormally(f, f.getException()); + }}; + checkInvoke(a); + } + + /** + * join of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkJoin() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingFibAction f = new FailingFibAction(8); + assertSame(f, f.fork()); + try { + f.join(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + checkInvoke(a); + } + + /** + * get of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkGet() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FailingFibAction f = new FailingFibAction(8); + assertSame(f, f.fork()); + try { + f.get(); + shouldThrow(); + } catch (ExecutionException success) { + Throwable cause = success.getCause(); + assertTrue(cause instanceof FJException); + checkCompletedAbnormally(f, cause); + } + }}; + checkInvoke(a); + } + + /** + * timed get of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkTimedGet() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FailingFibAction f = new FailingFibAction(8); + assertSame(f, f.fork()); + try { + f.get(5L, SECONDS); + shouldThrow(); + } catch (ExecutionException success) { + Throwable cause = success.getCause(); + assertTrue(cause instanceof FJException); + checkCompletedAbnormally(f, cause); + } + }}; + checkInvoke(a); + } + + /** + * quietlyJoin of a forked task returns when task completes abnormally + */ + public void testAbnormalForkQuietlyJoin() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingFibAction f = new FailingFibAction(8); + assertSame(f, f.fork()); + f.quietlyJoin(); + assertTrue(f.getException() instanceof FJException); + checkCompletedAbnormally(f, f.getException()); + }}; + checkInvoke(a); + } + + /** + * invoke task throws exception when task cancelled + */ + public void testCancelledInvoke() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + assertTrue(f.cancel(true)); + try { + f.invoke(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + checkInvoke(a); + } + + /** + * join of a forked task throws exception when task cancelled + */ + public void testCancelledForkJoin() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.join(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + checkInvoke(a); + } + + /** + * get of a forked task throws exception when task cancelled + */ + public void testCancelledForkGet() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FibAction f = new FibAction(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.get(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + checkInvoke(a); + } + + /** + * timed get of a forked task throws exception when task cancelled + */ + public void testCancelledForkTimedGet() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FibAction f = new FibAction(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.get(5L, SECONDS); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + checkInvoke(a); + } + + /** + * quietlyJoin of a forked task returns when task cancelled + */ + public void testCancelledForkQuietlyJoin() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + f.quietlyJoin(); + checkCancelled(f); + }}; + checkInvoke(a); + } + + /** + * inForkJoinPool of non-FJ task returns false + */ + public void testInForkJoinPool2() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + assertFalse(inForkJoinPool()); + }}; + assertNull(a.invoke()); + } + + /** + * A reinitialized normally completed task may be re-invoked + */ + public void testReinitialize() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + checkNotDone(f); + + for (int i = 0; i < 3; i++) { + assertNull(f.invoke()); + assertEquals(21, f.result); + checkCompletedNormally(f); + f.reinitialize(); + checkNotDone(f); + } + }}; + checkInvoke(a); + } + + /** + * A reinitialized abnormally completed task may be re-invoked + */ + public void testReinitializeAbnormal() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingFibAction f = new FailingFibAction(8); + checkNotDone(f); + + for (int i = 0; i < 3; i++) { + try { + f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + f.reinitialize(); + checkNotDone(f); + } + }}; + checkInvoke(a); + } + + /** + * invoke task throws exception after invoking completeExceptionally + */ + public void testCompleteExceptionally() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + f.completeExceptionally(new FJException()); + try { + f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + checkInvoke(a); + } + + /** + * invoke task suppresses execution invoking complete + */ + public void testComplete() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + f.complete(null); + assertNull(f.invoke()); + assertEquals(0, f.result); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * invokeAll(t1, t2) invokes all task arguments + */ + public void testInvokeAll2() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + FibAction g = new FibAction(9); + invokeAll(f, g); + checkCompletedNormally(f); + assertEquals(21, f.result); + checkCompletedNormally(g); + assertEquals(34, g.result); + }}; + checkInvoke(a); + } + + /** + * invokeAll(tasks) with 1 argument invokes task + */ + public void testInvokeAll1() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + invokeAll(f); + checkCompletedNormally(f); + assertEquals(21, f.result); + }}; + checkInvoke(a); + } + + /** + * invokeAll(tasks) with > 2 argument invokes tasks + */ + public void testInvokeAll3() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + FibAction g = new FibAction(9); + FibAction h = new FibAction(7); + invokeAll(f, g, h); + assertTrue(f.isDone()); + assertTrue(g.isDone()); + assertTrue(h.isDone()); + checkCompletedNormally(f); + assertEquals(21, f.result); + checkCompletedNormally(g); + assertEquals(34, g.result); + checkCompletedNormally(g); + assertEquals(13, h.result); + }}; + checkInvoke(a); + } + + /** + * invokeAll(collection) invokes all tasks in the collection + */ + public void testInvokeAllCollection() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + FibAction g = new FibAction(9); + FibAction h = new FibAction(7); + HashSet set = new HashSet(); + set.add(f); + set.add(g); + set.add(h); + invokeAll(set); + assertTrue(f.isDone()); + assertTrue(g.isDone()); + assertTrue(h.isDone()); + checkCompletedNormally(f); + assertEquals(21, f.result); + checkCompletedNormally(g); + assertEquals(34, g.result); + checkCompletedNormally(g); + assertEquals(13, h.result); + }}; + checkInvoke(a); + } + + /** + * invokeAll(tasks) with any null task throws NPE + */ + public void testInvokeAllNPE() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + FibAction g = new FibAction(9); + FibAction h = null; + try { + invokeAll(f, g, h); + shouldThrow(); + } catch (NullPointerException success) {} + }}; + checkInvoke(a); + } + + /** + * invokeAll(t1, t2) throw exception if any task does + */ + public void testAbnormalInvokeAll2() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + FailingFibAction g = new FailingFibAction(9); + try { + invokeAll(f, g); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + checkInvoke(a); + } + + /** + * invokeAll(tasks) with 1 argument throws exception if task does + */ + public void testAbnormalInvokeAll1() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingFibAction g = new FailingFibAction(9); + try { + invokeAll(g); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + checkInvoke(a); + } + + /** + * invokeAll(tasks) with > 2 argument throws exception if any task does + */ + public void testAbnormalInvokeAll3() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + FailingFibAction g = new FailingFibAction(9); + FibAction h = new FibAction(7); + try { + invokeAll(f, g, h); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + checkInvoke(a); + } + + /** + * invokeAll(collection) throws exception if any task does + */ + public void testAbnormalInvokeAllCollection() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingFibAction f = new FailingFibAction(8); + FibAction g = new FibAction(9); + FibAction h = new FibAction(7); + HashSet set = new HashSet(); + set.add(f); + set.add(g); + set.add(h); + try { + invokeAll(set); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + checkInvoke(a); + } + + // CountedCompleter versions + + abstract static class CCF extends CountedCompleter { + int number; + int rnumber; + + public CCF(CountedCompleter parent, int n) { + super(parent, 1); + this.number = n; + } + + public final void compute() { + CountedCompleter p; + CCF f = this; + int n = number; + while (n >= 2) { + new RCCF(f, n - 2).fork(); + f = new LCCF(f, --n); + } + f.number = n; + f.onCompletion(f); + if ((p = f.getCompleter()) != null) + p.tryComplete(); + else + f.quietlyComplete(); + } + } + + static final class LCCF extends CCF { + public LCCF(CountedCompleter parent, int n) { + super(parent, n); + } + public final void onCompletion(CountedCompleter caller) { + CCF p = (CCF)getCompleter(); + int n = number + rnumber; + if (p != null) + p.number = n; + else + number = n; + } + } + static final class RCCF extends CCF { + public RCCF(CountedCompleter parent, int n) { + super(parent, n); + } + public final void onCompletion(CountedCompleter caller) { + CCF p = (CCF)getCompleter(); + int n = number + rnumber; + if (p != null) + p.rnumber = n; + else + number = n; + } + } + + // Version of CCF with forced failure in left completions + abstract static class FailingCCF extends CountedCompleter { + int number; + int rnumber; + + public FailingCCF(CountedCompleter parent, int n) { + super(parent, 1); + this.number = n; + } + + public final void compute() { + CountedCompleter p; + FailingCCF f = this; + int n = number; + while (n >= 2) { + new RFCCF(f, n - 2).fork(); + f = new LFCCF(f, --n); + } + f.number = n; + f.onCompletion(f); + if ((p = f.getCompleter()) != null) + p.tryComplete(); + else + f.quietlyComplete(); + } + } + + static final class LFCCF extends FailingCCF { + public LFCCF(CountedCompleter parent, int n) { + super(parent, n); + } + public final void onCompletion(CountedCompleter caller) { + FailingCCF p = (FailingCCF)getCompleter(); + int n = number + rnumber; + if (p != null) + p.number = n; + else + number = n; + } + } + static final class RFCCF extends FailingCCF { + public RFCCF(CountedCompleter parent, int n) { + super(parent, n); + } + public final void onCompletion(CountedCompleter caller) { + completeExceptionally(new FJException()); + } + } + + /** + * invoke returns when task completes normally. + * isCompletedAbnormally and isCancelled return false for normally + * completed tasks; getRawResult returns null. + */ + public void testInvokeCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + assertNull(f.invoke()); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * quietlyInvoke task returns when task completes normally. + * isCompletedAbnormally and isCancelled return false for normally + * completed tasks + */ + public void testQuietlyInvokeCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + f.quietlyInvoke(); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * join of a forked task returns when task completes + */ + public void testForkJoinCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + assertSame(f, f.fork()); + assertNull(f.join()); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * get of a forked task returns when task completes + */ + public void testForkGetCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + CCF f = new LCCF(null, 8); + assertSame(f, f.fork()); + assertNull(f.get()); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * timed get of a forked task returns when task completes + */ + public void testForkTimedGetCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + CCF f = new LCCF(null, 8); + assertSame(f, f.fork()); + assertNull(f.get(LONG_DELAY_MS, MILLISECONDS)); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * timed get with null time unit throws NPE + */ + public void testForkTimedGetNPECC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + CCF f = new LCCF(null, 8); + assertSame(f, f.fork()); + try { + f.get(5L, null); + shouldThrow(); + } catch (NullPointerException success) {} + }}; + checkInvoke(a); + } + + /** + * quietlyJoin of a forked task returns when task completes + */ + public void testForkQuietlyJoinCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + assertSame(f, f.fork()); + f.quietlyJoin(); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + checkInvoke(a); + } + + /** + * invoke task throws exception when task completes abnormally + */ + public void testAbnormalInvokeCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingCCF f = new LFCCF(null, 8); + try { + f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + checkInvoke(a); + } + + /** + * quietlyInvoke task returns when task completes abnormally + */ + public void testAbnormalQuietlyInvokeCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingCCF f = new LFCCF(null, 8); + f.quietlyInvoke(); + assertTrue(f.getException() instanceof FJException); + checkCompletedAbnormally(f, f.getException()); + }}; + checkInvoke(a); + } + + /** + * join of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkJoinCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingCCF f = new LFCCF(null, 8); + assertSame(f, f.fork()); + try { + f.join(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + checkInvoke(a); + } + + /** + * get of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkGetCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FailingCCF f = new LFCCF(null, 8); + assertSame(f, f.fork()); + try { + f.get(); + shouldThrow(); + } catch (ExecutionException success) { + Throwable cause = success.getCause(); + assertTrue(cause instanceof FJException); + checkCompletedAbnormally(f, cause); + } + }}; + checkInvoke(a); + } + + /** + * timed get of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkTimedGetCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FailingCCF f = new LFCCF(null, 8); + assertSame(f, f.fork()); + try { + f.get(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (ExecutionException success) { + Throwable cause = success.getCause(); + assertTrue(cause instanceof FJException); + checkCompletedAbnormally(f, cause); + } + }}; + checkInvoke(a); + } + + /** + * quietlyJoin of a forked task returns when task completes abnormally + */ + public void testAbnormalForkQuietlyJoinCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingCCF f = new LFCCF(null, 8); + assertSame(f, f.fork()); + f.quietlyJoin(); + assertTrue(f.getException() instanceof FJException); + checkCompletedAbnormally(f, f.getException()); + }}; + checkInvoke(a); + } + + /** + * invoke task throws exception when task cancelled + */ + public void testCancelledInvokeCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + assertTrue(f.cancel(true)); + try { + f.invoke(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + checkInvoke(a); + } + + /** + * join of a forked task throws exception when task cancelled + */ + public void testCancelledForkJoinCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.join(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + checkInvoke(a); + } + + /** + * get of a forked task throws exception when task cancelled + */ + public void testCancelledForkGetCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + CCF f = new LCCF(null, 8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.get(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + checkInvoke(a); + } + + /** + * timed get of a forked task throws exception when task cancelled + */ + public void testCancelledForkTimedGetCC() throws Exception { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + CCF f = new LCCF(null, 8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.get(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + checkInvoke(a); + } + + /** + * quietlyJoin of a forked task returns when task cancelled + */ + public void testCancelledForkQuietlyJoinCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + f.quietlyJoin(); + checkCancelled(f); + }}; + checkInvoke(a); + } + + /** + * getPool of non-FJ task returns null + */ + public void testGetPool2CC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + assertNull(getPool()); + }}; + assertNull(a.invoke()); + } + + /** + * inForkJoinPool of non-FJ task returns false + */ + public void testInForkJoinPool2CC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + assertFalse(inForkJoinPool()); + }}; + assertNull(a.invoke()); + } + + /** + * setRawResult(null) succeeds + */ + public void testSetRawResultCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + setRawResult(null); + assertNull(getRawResult()); + }}; + assertNull(a.invoke()); + } + + /** + * invoke task throws exception after invoking completeExceptionally + */ + public void testCompleteExceptionally2CC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + f.completeExceptionally(new FJException()); + try { + f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + checkInvoke(a); + } + + /** + * invokeAll(t1, t2) invokes all task arguments + */ + public void testInvokeAll2CC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + CCF g = new LCCF(null, 9); + invokeAll(f, g); + assertEquals(21, f.number); + assertEquals(34, g.number); + checkCompletedNormally(f); + checkCompletedNormally(g); + }}; + checkInvoke(a); + } + + /** + * invokeAll(tasks) with 1 argument invokes task + */ + public void testInvokeAll1CC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + invokeAll(f); + checkCompletedNormally(f); + assertEquals(21, f.number); + }}; + checkInvoke(a); + } + + /** + * invokeAll(tasks) with > 2 argument invokes tasks + */ + public void testInvokeAll3CC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + CCF g = new LCCF(null, 9); + CCF h = new LCCF(null, 7); + invokeAll(f, g, h); + assertEquals(21, f.number); + assertEquals(34, g.number); + assertEquals(13, h.number); + checkCompletedNormally(f); + checkCompletedNormally(g); + checkCompletedNormally(h); + }}; + checkInvoke(a); + } + + /** + * invokeAll(collection) invokes all tasks in the collection + */ + public void testInvokeAllCollectionCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + CCF g = new LCCF(null, 9); + CCF h = new LCCF(null, 7); + HashSet set = new HashSet(); + set.add(f); + set.add(g); + set.add(h); + invokeAll(set); + assertEquals(21, f.number); + assertEquals(34, g.number); + assertEquals(13, h.number); + checkCompletedNormally(f); + checkCompletedNormally(g); + checkCompletedNormally(h); + }}; + checkInvoke(a); + } + + /** + * invokeAll(tasks) with any null task throws NPE + */ + public void testInvokeAllNPECC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + CCF g = new LCCF(null, 9); + CCF h = null; + try { + invokeAll(f, g, h); + shouldThrow(); + } catch (NullPointerException success) {} + }}; + checkInvoke(a); + } + + /** + * invokeAll(t1, t2) throw exception if any task does + */ + public void testAbnormalInvokeAll2CC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + FailingCCF g = new LFCCF(null, 9); + try { + invokeAll(f, g); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + checkInvoke(a); + } + + /** + * invokeAll(tasks) with 1 argument throws exception if task does + */ + public void testAbnormalInvokeAll1CC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingCCF g = new LFCCF(null, 9); + try { + invokeAll(g); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + checkInvoke(a); + } + + /** + * invokeAll(tasks) with > 2 argument throws exception if any task does + */ + public void testAbnormalInvokeAll3CC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + CCF f = new LCCF(null, 8); + FailingCCF g = new LFCCF(null, 9); + CCF h = new LCCF(null, 7); + try { + invokeAll(f, g, h); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + checkInvoke(a); + } + + /** + * invokeAll(collection) throws exception if any task does + */ + public void testAbnormalInvokeAllCollectionCC() { + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingCCF f = new LFCCF(null, 8); + CCF g = new LCCF(null, 9); + CCF h = new LCCF(null, 7); + HashSet set = new HashSet(); + set.add(f); + set.add(g); + set.add(h); + try { + invokeAll(set); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + checkInvoke(a); + } + + /** + * awaitQuiescence by a worker is equivalent in effect to + * ForkJoinTask.helpQuiesce() + */ + public void testAwaitQuiescence1() throws Exception { + final ForkJoinPool p = new ForkJoinPool(); + try (PoolCleaner cleaner = cleaner(p)) { + final long startTime = System.nanoTime(); + assertTrue(p.isQuiescent()); + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + assertSame(p, ForkJoinTask.getPool()); + boolean quiescent = p.awaitQuiescence(LONG_DELAY_MS, MILLISECONDS); + assertTrue(quiescent); + assertFalse(p.isQuiescent()); + while (!f.isDone()) { + assertFalse(p.getAsyncMode()); + assertFalse(p.isShutdown()); + assertFalse(p.isTerminating()); + assertFalse(p.isTerminated()); + Thread.yield(); + } + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + assertFalse(p.isQuiescent()); + assertEquals(0, ForkJoinTask.getQueuedTaskCount()); + assertEquals(21, f.result); + }}; + p.execute(a); + while (!a.isDone() || !p.isQuiescent()) { + assertFalse(p.getAsyncMode()); + assertFalse(p.isShutdown()); + assertFalse(p.isTerminating()); + assertFalse(p.isTerminated()); + Thread.yield(); + } + assertEquals(0, p.getQueuedTaskCount()); + assertFalse(p.getAsyncMode()); + assertEquals(0, p.getQueuedSubmissionCount()); + assertFalse(p.hasQueuedSubmissions()); + while (p.getActiveThreadCount() != 0 + && millisElapsedSince(startTime) < LONG_DELAY_MS) + Thread.yield(); + assertFalse(p.isShutdown()); + assertFalse(p.isTerminating()); + assertFalse(p.isTerminated()); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + } + + /** + * awaitQuiescence returns when pool isQuiescent() or the indicated + * timeout elapsed + */ + public void testAwaitQuiescence2() throws Exception { + /** + * """It is possible to disable or limit the use of threads in the + * common pool by setting the parallelism property to zero. However + * doing so may cause unjoined tasks to never be executed.""" + */ + if ("0".equals(System.getProperty( + "java.util.concurrent.ForkJoinPool.common.parallelism"))) + return; + final ForkJoinPool p = new ForkJoinPool(); + try (PoolCleaner cleaner = cleaner(p)) { + assertTrue(p.isQuiescent()); + final long startTime = System.nanoTime(); + ForkJoinTask a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + while (!f.isDone() + && millisElapsedSince(startTime) < LONG_DELAY_MS) { + assertFalse(p.getAsyncMode()); + assertFalse(p.isShutdown()); + assertFalse(p.isTerminating()); + assertFalse(p.isTerminated()); + Thread.yield(); + } + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + assertEquals(0, ForkJoinTask.getQueuedTaskCount()); + assertEquals(21, f.result); + }}; + p.execute(a); + assertTrue(p.awaitQuiescence(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isQuiescent()); + assertTrue(a.isDone()); + assertEquals(0, p.getQueuedTaskCount()); + assertFalse(p.getAsyncMode()); + assertEquals(0, p.getQueuedSubmissionCount()); + assertFalse(p.hasQueuedSubmissions()); + while (p.getActiveThreadCount() != 0 + && millisElapsedSince(startTime) < LONG_DELAY_MS) + Thread.yield(); + assertFalse(p.isShutdown()); + assertFalse(p.isTerminating()); + assertFalse(p.isTerminated()); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + } + +} diff --git a/jdk/test/java/util/concurrent/tck/ForkJoinPoolTest.java b/jdk/test/java/util/concurrent/tck/ForkJoinPoolTest.java new file mode 100644 index 00000000000..282557d97b7 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ForkJoinPoolTest.java @@ -0,0 +1,991 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +import java.security.PrivilegedAction; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinTask; +import java.util.concurrent.ForkJoinWorkerThread; +import java.util.concurrent.Future; +import java.util.concurrent.RecursiveTask; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantLock; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ForkJoinPoolTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + + public static Test suite() { + return new TestSuite(ForkJoinPoolTest.class); + } + + /* + * Testing coverage notes: + * + * 1. shutdown and related methods are tested via super.joinPool. + * + * 2. newTaskFor and adapters are tested in submit/invoke tests + * + * 3. We cannot portably test monitoring methods such as + * getStealCount() since they rely ultimately on random task + * stealing that may cause tasks not to be stolen/propagated + * across threads, especially on uniprocessors. + * + * 4. There are no independently testable ForkJoinWorkerThread + * methods, but they are covered here and in task tests. + */ + + // Some classes to test extension and factory methods + + static class MyHandler implements Thread.UncaughtExceptionHandler { + volatile int catches = 0; + public void uncaughtException(Thread t, Throwable e) { + ++catches; + } + } + + static class MyError extends Error {} + + // to test handlers + static class FailingFJWSubclass extends ForkJoinWorkerThread { + public FailingFJWSubclass(ForkJoinPool p) { super(p) ; } + protected void onStart() { super.onStart(); throw new MyError(); } + } + + static class FailingThreadFactory + implements ForkJoinPool.ForkJoinWorkerThreadFactory { + volatile int calls = 0; + public ForkJoinWorkerThread newThread(ForkJoinPool p) { + if (++calls > 1) return null; + return new FailingFJWSubclass(p); + } + } + + static class SubFJP extends ForkJoinPool { // to expose protected + SubFJP() { super(1); } + public int drainTasksTo(Collection> c) { + return super.drainTasksTo(c); + } + public ForkJoinTask pollSubmission() { + return super.pollSubmission(); + } + } + + static class ManagedLocker implements ForkJoinPool.ManagedBlocker { + final ReentrantLock lock; + boolean hasLock = false; + ManagedLocker(ReentrantLock lock) { this.lock = lock; } + public boolean block() { + if (!hasLock) + lock.lock(); + return true; + } + public boolean isReleasable() { + return hasLock || (hasLock = lock.tryLock()); + } + } + + // A simple recursive task for testing + static final class FibTask extends RecursiveTask { + final int number; + FibTask(int n) { number = n; } + protected Integer compute() { + int n = number; + if (n <= 1) + return n; + FibTask f1 = new FibTask(n - 1); + f1.fork(); + return (new FibTask(n - 2)).compute() + f1.join(); + } + } + + // A failing task for testing + static final class FailingTask extends ForkJoinTask { + public final Void getRawResult() { return null; } + protected final void setRawResult(Void mustBeNull) { } + protected final boolean exec() { throw new Error(); } + FailingTask() {} + } + + // Fib needlessly using locking to test ManagedBlockers + static final class LockingFibTask extends RecursiveTask { + final int number; + final ManagedLocker locker; + final ReentrantLock lock; + LockingFibTask(int n, ManagedLocker locker, ReentrantLock lock) { + number = n; + this.locker = locker; + this.lock = lock; + } + protected Integer compute() { + int n; + LockingFibTask f1 = null; + LockingFibTask f2 = null; + locker.block(); + n = number; + if (n > 1) { + f1 = new LockingFibTask(n - 1, locker, lock); + f2 = new LockingFibTask(n - 2, locker, lock); + } + lock.unlock(); + if (n <= 1) + return n; + else { + f1.fork(); + return f2.compute() + f1.join(); + } + } + } + + /** + * Successfully constructed pool reports default factory, + * parallelism and async mode policies, no active threads or + * tasks, and quiescent running state. + */ + public void testDefaultInitialState() { + ForkJoinPool p = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(p)) { + assertSame(ForkJoinPool.defaultForkJoinWorkerThreadFactory, + p.getFactory()); + assertFalse(p.getAsyncMode()); + assertEquals(0, p.getActiveThreadCount()); + assertEquals(0, p.getStealCount()); + assertEquals(0, p.getQueuedTaskCount()); + assertEquals(0, p.getQueuedSubmissionCount()); + assertFalse(p.hasQueuedSubmissions()); + assertFalse(p.isShutdown()); + assertFalse(p.isTerminating()); + assertFalse(p.isTerminated()); + } + } + + /** + * Constructor throws if size argument is less than zero + */ + public void testConstructor1() { + try { + new ForkJoinPool(-1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if factory argument is null + */ + public void testConstructor2() { + try { + new ForkJoinPool(1, null, null, false); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * getParallelism returns size set in constructor + */ + public void testGetParallelism() { + ForkJoinPool p = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(1, p.getParallelism()); + } + } + + /** + * getPoolSize returns number of started workers. + */ + public void testGetPoolSize() { + final CountDownLatch taskStarted = new CountDownLatch(1); + final CountDownLatch done = new CountDownLatch(1); + final ForkJoinPool p = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(0, p.getActiveThreadCount()); + final Runnable task = new CheckedRunnable() { + public void realRun() throws InterruptedException { + taskStarted.countDown(); + assertEquals(1, p.getPoolSize()); + assertEquals(1, p.getActiveThreadCount()); + done.await(); + }}; + Future future = p.submit(task); + await(taskStarted); + assertEquals(1, p.getPoolSize()); + assertEquals(1, p.getActiveThreadCount()); + done.countDown(); + } + assertEquals(0, p.getPoolSize()); + assertEquals(0, p.getActiveThreadCount()); + } + + /** + * awaitTermination on a non-shutdown pool times out + */ + public void testAwaitTermination_timesOut() throws InterruptedException { + ForkJoinPool p = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(p)) { + assertFalse(p.isTerminated()); + assertFalse(p.awaitTermination(Long.MIN_VALUE, NANOSECONDS)); + assertFalse(p.awaitTermination(Long.MIN_VALUE, MILLISECONDS)); + assertFalse(p.awaitTermination(-1L, NANOSECONDS)); + assertFalse(p.awaitTermination(-1L, MILLISECONDS)); + assertFalse(p.awaitTermination(0L, NANOSECONDS)); + assertFalse(p.awaitTermination(0L, MILLISECONDS)); + long timeoutNanos = 999999L; + long startTime = System.nanoTime(); + assertFalse(p.awaitTermination(timeoutNanos, NANOSECONDS)); + assertTrue(System.nanoTime() - startTime >= timeoutNanos); + assertFalse(p.isTerminated()); + startTime = System.nanoTime(); + long timeoutMillis = timeoutMillis(); + assertFalse(p.awaitTermination(timeoutMillis, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + assertFalse(p.isTerminated()); + p.shutdown(); + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + } + } + + /** + * setUncaughtExceptionHandler changes handler for uncaught exceptions. + * + * Additionally tests: Overriding ForkJoinWorkerThread.onStart + * performs its defined action + */ + public void testSetUncaughtExceptionHandler() throws InterruptedException { + final CountDownLatch uehInvoked = new CountDownLatch(1); + final Thread.UncaughtExceptionHandler ueh = + new Thread.UncaughtExceptionHandler() { + public void uncaughtException(Thread t, Throwable e) { + threadAssertTrue(e instanceof MyError); + threadAssertTrue(t instanceof FailingFJWSubclass); + uehInvoked.countDown(); + }}; + ForkJoinPool p = new ForkJoinPool(1, new FailingThreadFactory(), + ueh, false); + try (PoolCleaner cleaner = cleaner(p)) { + assertSame(ueh, p.getUncaughtExceptionHandler()); + try { + p.execute(new FibTask(8)); + await(uehInvoked); + } finally { + p.shutdownNow(); // failure might have prevented processing task + } + } + } + + /** + * After invoking a single task, isQuiescent eventually becomes + * true, at which time queues are empty, threads are not active, + * the task has completed successfully, and construction + * parameters continue to hold + */ + public void testIsQuiescent() throws Exception { + ForkJoinPool p = new ForkJoinPool(2); + try (PoolCleaner cleaner = cleaner(p)) { + assertTrue(p.isQuiescent()); + long startTime = System.nanoTime(); + FibTask f = new FibTask(20); + p.invoke(f); + assertSame(ForkJoinPool.defaultForkJoinWorkerThreadFactory, + p.getFactory()); + while (! p.isQuiescent()) { + if (millisElapsedSince(startTime) > LONG_DELAY_MS) + throw new AssertionFailedError("timed out"); + assertFalse(p.getAsyncMode()); + assertFalse(p.isShutdown()); + assertFalse(p.isTerminating()); + assertFalse(p.isTerminated()); + Thread.yield(); + } + + assertTrue(p.isQuiescent()); + assertFalse(p.getAsyncMode()); + assertEquals(0, p.getQueuedTaskCount()); + assertEquals(0, p.getQueuedSubmissionCount()); + assertFalse(p.hasQueuedSubmissions()); + while (p.getActiveThreadCount() != 0 + && millisElapsedSince(startTime) < LONG_DELAY_MS) + Thread.yield(); + assertFalse(p.isShutdown()); + assertFalse(p.isTerminating()); + assertFalse(p.isTerminated()); + assertTrue(f.isDone()); + assertEquals(6765, (int) f.get()); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + } + + /** + * Completed submit(ForkJoinTask) returns result + */ + public void testSubmitForkJoinTask() throws Throwable { + ForkJoinPool p = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(p)) { + ForkJoinTask f = p.submit(new FibTask(8)); + assertEquals(21, (int) f.get()); + } + } + + /** + * A task submitted after shutdown is rejected + */ + public void testSubmitAfterShutdown() { + ForkJoinPool p = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(p)) { + p.shutdown(); + assertTrue(p.isShutdown()); + try { + ForkJoinTask f = p.submit(new FibTask(8)); + shouldThrow(); + } catch (RejectedExecutionException success) {} + } + } + + /** + * Pool maintains parallelism when using ManagedBlocker + */ + public void testBlockingForkJoinTask() throws Throwable { + ForkJoinPool p = new ForkJoinPool(4); + try { + ReentrantLock lock = new ReentrantLock(); + ManagedLocker locker = new ManagedLocker(lock); + ForkJoinTask f = new LockingFibTask(20, locker, lock); + p.execute(f); + assertEquals(6765, (int) f.get()); + } finally { + p.shutdownNow(); // don't wait out shutdown + } + } + + /** + * pollSubmission returns unexecuted submitted task, if present + */ + public void testPollSubmission() { + final CountDownLatch done = new CountDownLatch(1); + SubFJP p = new SubFJP(); + try (PoolCleaner cleaner = cleaner(p)) { + ForkJoinTask a = p.submit(awaiter(done)); + ForkJoinTask b = p.submit(awaiter(done)); + ForkJoinTask c = p.submit(awaiter(done)); + ForkJoinTask r = p.pollSubmission(); + assertTrue(r == a || r == b || r == c); + assertFalse(r.isDone()); + done.countDown(); + } + } + + /** + * drainTasksTo transfers unexecuted submitted tasks, if present + */ + public void testDrainTasksTo() { + final CountDownLatch done = new CountDownLatch(1); + SubFJP p = new SubFJP(); + try (PoolCleaner cleaner = cleaner(p)) { + ForkJoinTask a = p.submit(awaiter(done)); + ForkJoinTask b = p.submit(awaiter(done)); + ForkJoinTask c = p.submit(awaiter(done)); + ArrayList al = new ArrayList(); + p.drainTasksTo(al); + assertTrue(al.size() > 0); + for (ForkJoinTask r : al) { + assertTrue(r == a || r == b || r == c); + assertFalse(r.isDone()); + } + done.countDown(); + } + } + + // FJ Versions of AbstractExecutorService tests + + /** + * execute(runnable) runs it to completion + */ + public void testExecuteRunnable() throws Throwable { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + final AtomicBoolean done = new AtomicBoolean(false); + Future future = e.submit(new CheckedRunnable() { + public void realRun() { + done.set(true); + }}); + assertNull(future.get()); + assertNull(future.get(0, MILLISECONDS)); + assertTrue(done.get()); + assertTrue(future.isDone()); + assertFalse(future.isCancelled()); + } + } + + /** + * Completed submit(callable) returns result + */ + public void testSubmitCallable() throws Throwable { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + Future future = e.submit(new StringTask()); + assertSame(TEST_STRING, future.get()); + assertTrue(future.isDone()); + assertFalse(future.isCancelled()); + } + } + + /** + * Completed submit(runnable) returns successfully + */ + public void testSubmitRunnable() throws Throwable { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + Future future = e.submit(new NoOpRunnable()); + assertNull(future.get()); + assertTrue(future.isDone()); + assertFalse(future.isCancelled()); + } + } + + /** + * Completed submit(runnable, result) returns result + */ + public void testSubmitRunnable2() throws Throwable { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + Future future = e.submit(new NoOpRunnable(), TEST_STRING); + assertSame(TEST_STRING, future.get()); + assertTrue(future.isDone()); + assertFalse(future.isCancelled()); + } + } + + /** + * A submitted privileged action runs to completion + */ + public void testSubmitPrivilegedAction() throws Exception { + final Callable callable = Executors.callable(new PrivilegedAction() { + public Object run() { return TEST_STRING; }}); + Runnable r = new CheckedRunnable() { + public void realRun() throws Exception { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + Future future = e.submit(callable); + assertSame(TEST_STRING, future.get()); + } + }}; + + runWithPermissions(r, new RuntimePermission("modifyThread")); + } + + /** + * A submitted privileged exception action runs to completion + */ + public void testSubmitPrivilegedExceptionAction() throws Exception { + final Callable callable = + Executors.callable(new PrivilegedExceptionAction() { + public Object run() { return TEST_STRING; }}); + Runnable r = new CheckedRunnable() { + public void realRun() throws Exception { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + Future future = e.submit(callable); + assertSame(TEST_STRING, future.get()); + } + }}; + + runWithPermissions(r, new RuntimePermission("modifyThread")); + } + + /** + * A submitted failed privileged exception action reports exception + */ + public void testSubmitFailedPrivilegedExceptionAction() throws Exception { + final Callable callable = + Executors.callable(new PrivilegedExceptionAction() { + public Object run() { throw new IndexOutOfBoundsException(); }}); + Runnable r = new CheckedRunnable() { + public void realRun() throws Exception { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + Future future = e.submit(callable); + try { + future.get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof IndexOutOfBoundsException); + } + } + }}; + + runWithPermissions(r, new RuntimePermission("modifyThread")); + } + + /** + * execute(null runnable) throws NullPointerException + */ + public void testExecuteNullRunnable() { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + try { + Future future = e.submit((Runnable) null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * submit(null callable) throws NullPointerException + */ + public void testSubmitNullCallable() { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + try { + Future future = e.submit((Callable) null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * submit(callable).get() throws InterruptedException if interrupted + */ + public void testInterruptedSubmit() throws InterruptedException { + final CountDownLatch submitted = new CountDownLatch(1); + final CountDownLatch quittingTime = new CountDownLatch(1); + final Callable awaiter = new CheckedCallable() { + public Void realCall() throws InterruptedException { + assertTrue(quittingTime.await(2*LONG_DELAY_MS, MILLISECONDS)); + return null; + }}; + final ExecutorService p = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(p, quittingTime)) { + Thread t = new Thread(new CheckedInterruptedRunnable() { + public void realRun() throws Exception { + Future future = p.submit(awaiter); + submitted.countDown(); + future.get(); + }}); + t.start(); + await(submitted); + t.interrupt(); + awaitTermination(t); + } + } + + /** + * get of submit(callable) throws ExecutionException if callable + * throws exception + */ + public void testSubmitEE() throws Throwable { + ForkJoinPool p = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.submit(new Callable() { + public Object call() { throw new ArithmeticException(); }}) + .get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof ArithmeticException); + } + } + } + + /** + * invokeAny(null) throws NullPointerException + */ + public void testInvokeAny1() throws Throwable { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * invokeAny(empty collection) throws IllegalArgumentException + */ + public void testInvokeAny2() throws Throwable { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(new ArrayList>()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * invokeAny(c) throws NullPointerException if c has a single null element + */ + public void testInvokeAny3() throws Throwable { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(null); + try { + e.invokeAny(l); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * invokeAny(c) throws NullPointerException if c has null elements + */ + public void testInvokeAny4() throws Throwable { + CountDownLatch latch = new CountDownLatch(1); + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(latchAwaitingStringTask(latch)); + l.add(null); + try { + e.invokeAny(l); + shouldThrow(); + } catch (NullPointerException success) {} + latch.countDown(); + } + } + + /** + * invokeAny(c) throws ExecutionException if no task in c completes + */ + public void testInvokeAny5() throws Throwable { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + try { + e.invokeAny(l); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + } + } + + /** + * invokeAny(c) returns result of some task in c if at least one completes + */ + public void testInvokeAny6() throws Throwable { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(new StringTask()); + String result = e.invokeAny(l); + assertSame(TEST_STRING, result); + } + } + + /** + * invokeAll(null) throws NullPointerException + */ + public void testInvokeAll1() throws Throwable { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAll(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * invokeAll(empty collection) returns empty collection + */ + public void testInvokeAll2() throws InterruptedException { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + List> r + = e.invokeAll(new ArrayList>()); + assertTrue(r.isEmpty()); + } + } + + /** + * invokeAll(c) throws NullPointerException if c has null elements + */ + public void testInvokeAll3() throws InterruptedException { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(null); + try { + e.invokeAll(l); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * get of returned element of invokeAll(c) throws + * ExecutionException on failed task + */ + public void testInvokeAll4() throws Throwable { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + List> futures = e.invokeAll(l); + assertEquals(1, futures.size()); + try { + futures.get(0).get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + } + } + + /** + * invokeAll(c) returns results of all completed tasks in c + */ + public void testInvokeAll5() throws Throwable { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(new StringTask()); + List> futures = e.invokeAll(l); + assertEquals(2, futures.size()); + for (Future future : futures) + assertSame(TEST_STRING, future.get()); + } + } + + /** + * timed invokeAny(null) throws NullPointerException + */ + public void testTimedInvokeAny1() throws Throwable { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(null, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed invokeAny(null time unit) throws NullPointerException + */ + public void testTimedInvokeAnyNullTimeUnit() throws Throwable { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + try { + e.invokeAny(l, MEDIUM_DELAY_MS, null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed invokeAny(empty collection) throws IllegalArgumentException + */ + public void testTimedInvokeAny2() throws Throwable { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(new ArrayList>(), + MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * timed invokeAny(c) throws NullPointerException if c has null elements + */ + public void testTimedInvokeAny3() throws Throwable { + CountDownLatch latch = new CountDownLatch(1); + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(latchAwaitingStringTask(latch)); + l.add(null); + try { + e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + latch.countDown(); + } + } + + /** + * timed invokeAny(c) throws ExecutionException if no task completes + */ + public void testTimedInvokeAny4() throws Throwable { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + long startTime = System.nanoTime(); + List> l = new ArrayList>(); + l.add(new NPETask()); + try { + e.invokeAny(l, LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + } + + /** + * timed invokeAny(c) returns result of some task in c + */ + public void testTimedInvokeAny5() throws Throwable { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + long startTime = System.nanoTime(); + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(new StringTask()); + String result = e.invokeAny(l, LONG_DELAY_MS, MILLISECONDS); + assertSame(TEST_STRING, result); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + } + + /** + * timed invokeAll(null) throws NullPointerException + */ + public void testTimedInvokeAll1() throws Throwable { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAll(null, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed invokeAll(null time unit) throws NullPointerException + */ + public void testTimedInvokeAllNullTimeUnit() throws Throwable { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + try { + e.invokeAll(l, MEDIUM_DELAY_MS, null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed invokeAll(empty collection) returns empty collection + */ + public void testTimedInvokeAll2() throws InterruptedException { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + List> r + = e.invokeAll(new ArrayList>(), + MEDIUM_DELAY_MS, MILLISECONDS); + assertTrue(r.isEmpty()); + } + } + + /** + * timed invokeAll(c) throws NullPointerException if c has null elements + */ + public void testTimedInvokeAll3() throws InterruptedException { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(null); + try { + e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * get of returned element of invokeAll(c) throws exception on failed task + */ + public void testTimedInvokeAll4() throws Throwable { + ExecutorService e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + List> futures + = e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS); + assertEquals(1, futures.size()); + try { + futures.get(0).get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + } + } + + /** + * timed invokeAll(c) returns results of all completed tasks in c + */ + public void testTimedInvokeAll5() throws Throwable { + ForkJoinPool e = new ForkJoinPool(1); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(new StringTask()); + List> futures + = e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS); + assertEquals(2, futures.size()); + for (Future future : futures) + assertSame(TEST_STRING, future.get()); + } + } + +} diff --git a/jdk/test/java/util/concurrent/tck/ForkJoinTask8Test.java b/jdk/test/java/util/concurrent/tck/ForkJoinTask8Test.java new file mode 100644 index 00000000000..c8fe1bb9986 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ForkJoinTask8Test.java @@ -0,0 +1,1228 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; + +import java.util.Arrays; +import java.util.Collections; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinTask; +import java.util.concurrent.ForkJoinWorkerThread; +import java.util.concurrent.RecursiveAction; +import java.util.concurrent.TimeoutException; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ForkJoinTask8Test extends JSR166TestCase { + + /* + * Testing notes: This differs from ForkJoinTaskTest mainly by + * defining a version of BinaryAsyncAction that uses JDK8 task + * tags for control state, thereby testing getForkJoinTaskTag, + * setForkJoinTaskTag, and compareAndSetForkJoinTaskTag across + * various contexts. Most of the test methods using it are + * otherwise identical, but omitting retest of those dealing with + * cancellation, which is not represented in this tag scheme. + */ + + static final short INITIAL_STATE = -1; + static final short COMPLETE_STATE = 0; + static final short EXCEPTION_STATE = 1; + + public static void main(String[] args) { + main(suite(), args); + } + + public static Test suite() { + return new TestSuite(ForkJoinTask8Test.class); + } + + // Runs with "mainPool" use > 1 thread. singletonPool tests use 1 + static final int mainPoolSize = + Math.max(2, Runtime.getRuntime().availableProcessors()); + + private static ForkJoinPool mainPool() { + return new ForkJoinPool(mainPoolSize); + } + + private static ForkJoinPool singletonPool() { + return new ForkJoinPool(1); + } + + private static ForkJoinPool asyncSingletonPool() { + return new ForkJoinPool(1, + ForkJoinPool.defaultForkJoinWorkerThreadFactory, + null, true); + } + + // Compute fib naively and efficiently + final int[] fib; + { + int[] fib = new int[10]; + fib[0] = 0; + fib[1] = 1; + for (int i = 2; i < fib.length; i++) + fib[i] = fib[i - 1] + fib[i - 2]; + this.fib = fib; + } + + private void testInvokeOnPool(ForkJoinPool pool, RecursiveAction a) { + try (PoolCleaner cleaner = cleaner(pool)) { + assertFalse(a.isDone()); + assertFalse(a.isCompletedNormally()); + assertFalse(a.isCompletedAbnormally()); + assertFalse(a.isCancelled()); + assertNull(a.getException()); + assertNull(a.getRawResult()); + + assertNull(pool.invoke(a)); + + assertTrue(a.isDone()); + assertTrue(a.isCompletedNormally()); + assertFalse(a.isCompletedAbnormally()); + assertFalse(a.isCancelled()); + assertNull(a.getException()); + assertNull(a.getRawResult()); + } + } + + void checkNotDone(ForkJoinTask a) { + assertFalse(a.isDone()); + assertFalse(a.isCompletedNormally()); + assertFalse(a.isCompletedAbnormally()); + assertFalse(a.isCancelled()); + assertNull(a.getException()); + assertNull(a.getRawResult()); + if (a instanceof BinaryAsyncAction) + assertTrue(((BinaryAsyncAction)a).getForkJoinTaskTag() == INITIAL_STATE); + + try { + a.get(0L, SECONDS); + shouldThrow(); + } catch (TimeoutException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + void checkCompletedNormally(ForkJoinTask a) { + checkCompletedNormally(a, null); + } + + void checkCompletedNormally(ForkJoinTask a, T expected) { + assertTrue(a.isDone()); + assertFalse(a.isCancelled()); + assertTrue(a.isCompletedNormally()); + assertFalse(a.isCompletedAbnormally()); + assertNull(a.getException()); + assertSame(expected, a.getRawResult()); + if (a instanceof BinaryAsyncAction) + assertTrue(((BinaryAsyncAction)a).getForkJoinTaskTag() == COMPLETE_STATE); + + { + Thread.currentThread().interrupt(); + long startTime = System.nanoTime(); + assertSame(expected, a.join()); + assertTrue(millisElapsedSince(startTime) < SMALL_DELAY_MS); + Thread.interrupted(); + } + + { + Thread.currentThread().interrupt(); + long startTime = System.nanoTime(); + a.quietlyJoin(); // should be no-op + assertTrue(millisElapsedSince(startTime) < SMALL_DELAY_MS); + Thread.interrupted(); + } + + assertFalse(a.cancel(false)); + assertFalse(a.cancel(true)); + try { + assertSame(expected, a.get()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + try { + assertSame(expected, a.get(5L, SECONDS)); + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + void checkCompletedAbnormally(ForkJoinTask a, Throwable t) { + assertTrue(a.isDone()); + assertFalse(a.isCancelled()); + assertFalse(a.isCompletedNormally()); + assertTrue(a.isCompletedAbnormally()); + assertSame(t.getClass(), a.getException().getClass()); + assertNull(a.getRawResult()); + assertFalse(a.cancel(false)); + assertFalse(a.cancel(true)); + if (a instanceof BinaryAsyncAction) + assertTrue(((BinaryAsyncAction)a).getForkJoinTaskTag() != INITIAL_STATE); + + try { + Thread.currentThread().interrupt(); + a.join(); + shouldThrow(); + } catch (Throwable expected) { + assertSame(t.getClass(), expected.getClass()); + } + Thread.interrupted(); + + { + long startTime = System.nanoTime(); + a.quietlyJoin(); // should be no-op + assertTrue(millisElapsedSince(startTime) < SMALL_DELAY_MS); + } + + try { + a.get(); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(t.getClass(), success.getCause().getClass()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + + try { + a.get(5L, SECONDS); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(t.getClass(), success.getCause().getClass()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + public static final class FJException extends RuntimeException { + FJException() { super(); } + } + + abstract static class BinaryAsyncAction extends ForkJoinTask { + + private volatile BinaryAsyncAction parent; + + private volatile BinaryAsyncAction sibling; + + protected BinaryAsyncAction() { + setForkJoinTaskTag(INITIAL_STATE); + } + + public final Void getRawResult() { return null; } + protected final void setRawResult(Void mustBeNull) { } + + public final void linkSubtasks(BinaryAsyncAction x, BinaryAsyncAction y) { + x.parent = y.parent = this; + x.sibling = y; + y.sibling = x; + } + + protected void onComplete(BinaryAsyncAction x, BinaryAsyncAction y) { + if (this.getForkJoinTaskTag() != COMPLETE_STATE || + x.getForkJoinTaskTag() != COMPLETE_STATE || + y.getForkJoinTaskTag() != COMPLETE_STATE) { + completeThisExceptionally(new FJException()); + } + } + + protected boolean onException() { + return true; + } + + public void linkAndForkSubtasks(BinaryAsyncAction x, BinaryAsyncAction y) { + linkSubtasks(x, y); + y.fork(); + x.fork(); + } + + private void completeThis() { + setForkJoinTaskTag(COMPLETE_STATE); + super.complete(null); + } + + private void completeThisExceptionally(Throwable ex) { + setForkJoinTaskTag(EXCEPTION_STATE); + super.completeExceptionally(ex); + } + + public boolean cancel(boolean mayInterruptIfRunning) { + if (super.cancel(mayInterruptIfRunning)) { + completeExceptionally(new FJException()); + return true; + } + return false; + } + + public final void complete() { + BinaryAsyncAction a = this; + for (;;) { + BinaryAsyncAction s = a.sibling; + BinaryAsyncAction p = a.parent; + a.sibling = null; + a.parent = null; + a.completeThis(); + if (p == null || + p.compareAndSetForkJoinTaskTag(INITIAL_STATE, COMPLETE_STATE)) + break; + try { + p.onComplete(a, s); + } catch (Throwable rex) { + p.completeExceptionally(rex); + return; + } + a = p; + } + } + + public final void completeExceptionally(Throwable ex) { + for (BinaryAsyncAction a = this;;) { + a.completeThisExceptionally(ex); + BinaryAsyncAction s = a.sibling; + if (s != null && !s.isDone()) + s.completeExceptionally(ex); + if ((a = a.parent) == null) + break; + } + } + + public final BinaryAsyncAction getParent() { + return parent; + } + + public BinaryAsyncAction getSibling() { + return sibling; + } + + public void reinitialize() { + parent = sibling = null; + super.reinitialize(); + } + + } + + final class AsyncFib extends BinaryAsyncAction { + int number; + int expectedResult; + public AsyncFib(int number) { + this.number = number; + this.expectedResult = fib[number]; + } + + public final boolean exec() { + try { + AsyncFib f = this; + int n = f.number; + while (n > 1) { + AsyncFib p = f; + AsyncFib r = new AsyncFib(n - 2); + f = new AsyncFib(--n); + p.linkSubtasks(r, f); + r.fork(); + } + f.complete(); + } + catch (Throwable ex) { + compareAndSetForkJoinTaskTag(INITIAL_STATE, EXCEPTION_STATE); + } + if (getForkJoinTaskTag() == EXCEPTION_STATE) + throw new FJException(); + return false; + } + + protected void onComplete(BinaryAsyncAction x, BinaryAsyncAction y) { + number = ((AsyncFib)x).number + ((AsyncFib)y).number; + super.onComplete(x, y); + } + + public void checkCompletedNormally() { + assertEquals(expectedResult, number); + ForkJoinTask8Test.this.checkCompletedNormally(this); + } + } + + static final class FailingAsyncFib extends BinaryAsyncAction { + int number; + public FailingAsyncFib(int n) { + this.number = n; + } + + public final boolean exec() { + try { + FailingAsyncFib f = this; + int n = f.number; + while (n > 1) { + FailingAsyncFib p = f; + FailingAsyncFib r = new FailingAsyncFib(n - 2); + f = new FailingAsyncFib(--n); + p.linkSubtasks(r, f); + r.fork(); + } + f.complete(); + } + catch (Throwable ex) { + compareAndSetForkJoinTaskTag(INITIAL_STATE, EXCEPTION_STATE); + } + if (getForkJoinTaskTag() == EXCEPTION_STATE) + throw new FJException(); + return false; + } + + protected void onComplete(BinaryAsyncAction x, BinaryAsyncAction y) { + completeExceptionally(new FJException()); + } + } + + /** + * invoke returns when task completes normally. + * isCompletedAbnormally and isCancelled return false for normally + * completed tasks; getRawResult returns null. + */ + public void testInvoke() { + testInvoke(mainPool()); + } + public void testInvoke_Singleton() { + testInvoke(singletonPool()); + } + public void testInvoke(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + assertNull(f.invoke()); + f.checkCompletedNormally(); + }}; + testInvokeOnPool(pool, a); + } + + /** + * quietlyInvoke task returns when task completes normally. + * isCompletedAbnormally and isCancelled return false for normally + * completed tasks + */ + public void testQuietlyInvoke() { + testQuietlyInvoke(mainPool()); + } + public void testQuietlyInvoke_Singleton() { + testQuietlyInvoke(singletonPool()); + } + public void testQuietlyInvoke(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + f.quietlyInvoke(); + f.checkCompletedNormally(); + }}; + testInvokeOnPool(pool, a); + } + + /** + * join of a forked task returns when task completes + */ + public void testForkJoin() { + testForkJoin(mainPool()); + } + public void testForkJoin_Singleton() { + testForkJoin(singletonPool()); + } + public void testForkJoin(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertNull(f.join()); + f.checkCompletedNormally(); + }}; + testInvokeOnPool(pool, a); + } + + /** + * get of a forked task returns when task completes + */ + public void testForkGet() { + testForkGet(mainPool()); + } + public void testForkGet_Singleton() { + testForkGet(singletonPool()); + } + public void testForkGet(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertNull(f.get()); + f.checkCompletedNormally(); + }}; + testInvokeOnPool(pool, a); + } + + /** + * timed get of a forked task returns when task completes + */ + public void testForkTimedGet() { + testForkTimedGet(mainPool()); + } + public void testForkTimedGet_Singleton() { + testForkTimedGet(singletonPool()); + } + public void testForkTimedGet(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertNull(f.get(LONG_DELAY_MS, MILLISECONDS)); + f.checkCompletedNormally(); + }}; + testInvokeOnPool(pool, a); + } + + /** + * timed get with null time unit throws NullPointerException + */ + public void testForkTimedGetNullTimeUnit() { + testForkTimedGetNullTimeUnit(mainPool()); + } + public void testForkTimedGetNullTimeUnit_Singleton() { + testForkTimedGet(singletonPool()); + } + public void testForkTimedGetNullTimeUnit(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + try { + f.get(5L, null); + shouldThrow(); + } catch (NullPointerException success) {} + }}; + testInvokeOnPool(pool, a); + } + + /** + * quietlyJoin of a forked task returns when task completes + */ + public void testForkQuietlyJoin() { + testForkQuietlyJoin(mainPool()); + } + public void testForkQuietlyJoin_Singleton() { + testForkQuietlyJoin(singletonPool()); + } + public void testForkQuietlyJoin(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + f.quietlyJoin(); + f.checkCompletedNormally(); + }}; + testInvokeOnPool(pool, a); + } + + /** + * helpQuiesce returns when tasks are complete. + * getQueuedTaskCount returns 0 when quiescent + */ + public void testForkHelpQuiesce() { + testForkHelpQuiesce(mainPool()); + } + public void testForkHelpQuiesce_Singleton() { + testForkHelpQuiesce(singletonPool()); + } + public void testForkHelpQuiesce(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + helpQuiesce(); + assertEquals(0, getQueuedTaskCount()); + f.checkCompletedNormally(); + }}; + testInvokeOnPool(pool, a); + } + + /** + * invoke task throws exception when task completes abnormally + */ + public void testAbnormalInvoke() { + testAbnormalInvoke(mainPool()); + } + public void testAbnormalInvoke_Singleton() { + testAbnormalInvoke(singletonPool()); + } + public void testAbnormalInvoke(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingAsyncFib f = new FailingAsyncFib(8); + try { + f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(pool, a); + } + + /** + * quietlyInvoke task returns when task completes abnormally + */ + public void testAbnormalQuietlyInvoke() { + testAbnormalQuietlyInvoke(mainPool()); + } + public void testAbnormalQuietlyInvoke_Singleton() { + testAbnormalQuietlyInvoke(singletonPool()); + } + public void testAbnormalQuietlyInvoke(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingAsyncFib f = new FailingAsyncFib(8); + f.quietlyInvoke(); + assertTrue(f.getException() instanceof FJException); + checkCompletedAbnormally(f, f.getException()); + }}; + testInvokeOnPool(pool, a); + } + + /** + * join of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkJoin() { + testAbnormalForkJoin(mainPool()); + } + public void testAbnormalForkJoin_Singleton() { + testAbnormalForkJoin(singletonPool()); + } + public void testAbnormalForkJoin(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingAsyncFib f = new FailingAsyncFib(8); + assertSame(f, f.fork()); + try { + f.join(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(pool, a); + } + + /** + * get of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkGet() { + testAbnormalForkGet(mainPool()); + } + public void testAbnormalForkGet_Singleton() { + testAbnormalForkJoin(singletonPool()); + } + public void testAbnormalForkGet(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FailingAsyncFib f = new FailingAsyncFib(8); + assertSame(f, f.fork()); + try { + f.get(); + shouldThrow(); + } catch (ExecutionException success) { + Throwable cause = success.getCause(); + assertTrue(cause instanceof FJException); + checkCompletedAbnormally(f, cause); + } + }}; + testInvokeOnPool(pool, a); + } + + /** + * timed get of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkTimedGet() { + testAbnormalForkTimedGet(mainPool()); + } + public void testAbnormalForkTimedGet_Singleton() { + testAbnormalForkTimedGet(singletonPool()); + } + public void testAbnormalForkTimedGet(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FailingAsyncFib f = new FailingAsyncFib(8); + assertSame(f, f.fork()); + try { + f.get(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (ExecutionException success) { + Throwable cause = success.getCause(); + assertTrue(cause instanceof FJException); + checkCompletedAbnormally(f, cause); + } + }}; + testInvokeOnPool(pool, a); + } + + /** + * quietlyJoin of a forked task returns when task completes abnormally + */ + public void testAbnormalForkQuietlyJoin() { + testAbnormalForkQuietlyJoin(mainPool()); + } + public void testAbnormalForkQuietlyJoin_Singleton() { + testAbnormalForkQuietlyJoin(singletonPool()); + } + public void testAbnormalForkQuietlyJoin(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingAsyncFib f = new FailingAsyncFib(8); + assertSame(f, f.fork()); + f.quietlyJoin(); + assertTrue(f.getException() instanceof FJException); + checkCompletedAbnormally(f, f.getException()); + }}; + testInvokeOnPool(pool, a); + } + + /** + * getPool of executing task returns its pool + */ + public void testGetPool() { + testGetPool(mainPool()); + } + public void testGetPool_Singleton() { + testGetPool(singletonPool()); + } + public void testGetPool(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + assertSame(pool, getPool()); + }}; + testInvokeOnPool(pool, a); + } + + /** + * getPool of non-FJ task returns null + */ + public void testGetPool2() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + assertNull(getPool()); + }}; + assertNull(a.invoke()); + } + + /** + * inForkJoinPool of executing task returns true + */ + public void testInForkJoinPool() { + testInForkJoinPool(mainPool()); + } + public void testInForkJoinPool_Singleton() { + testInForkJoinPool(singletonPool()); + } + public void testInForkJoinPool(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + assertTrue(inForkJoinPool()); + }}; + testInvokeOnPool(pool, a); + } + + /** + * inForkJoinPool of non-FJ task returns false + */ + public void testInForkJoinPool2() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + assertFalse(inForkJoinPool()); + }}; + assertNull(a.invoke()); + } + + /** + * setRawResult(null) succeeds + */ + public void testSetRawResult() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + setRawResult(null); + assertNull(getRawResult()); + }}; + assertNull(a.invoke()); + } + + /** + * invoke task throws exception after invoking completeExceptionally + */ + public void testCompleteExceptionally() { + testCompleteExceptionally(mainPool()); + } + public void testCompleteExceptionally_Singleton() { + testCompleteExceptionally(singletonPool()); + } + public void testCompleteExceptionally(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + f.completeExceptionally(new FJException()); + try { + f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(pool, a); + } + + /** + * invokeAll(tasks) with 1 argument invokes task + */ + public void testInvokeAll1() { + testInvokeAll1(mainPool()); + } + public void testInvokeAll1_Singleton() { + testInvokeAll1(singletonPool()); + } + public void testInvokeAll1(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + invokeAll(f); + f.checkCompletedNormally(); + }}; + testInvokeOnPool(pool, a); + } + + /** + * invokeAll(t1, t2) invokes all task arguments + */ + public void testInvokeAll2() { + testInvokeAll2(mainPool()); + } + public void testInvokeAll2_Singleton() { + testInvokeAll2(singletonPool()); + } + public void testInvokeAll2(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib[] tasks = { + new AsyncFib(8), + new AsyncFib(9), + }; + invokeAll(tasks[0], tasks[1]); + for (AsyncFib task : tasks) assertTrue(task.isDone()); + for (AsyncFib task : tasks) task.checkCompletedNormally(); + }}; + testInvokeOnPool(pool, a); + } + + /** + * invokeAll(tasks) with > 2 argument invokes tasks + */ + public void testInvokeAll3() { + testInvokeAll3(mainPool()); + } + public void testInvokeAll3_Singleton() { + testInvokeAll3(singletonPool()); + } + public void testInvokeAll3(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib[] tasks = { + new AsyncFib(8), + new AsyncFib(9), + new AsyncFib(7), + }; + invokeAll(tasks[0], tasks[1], tasks[2]); + for (AsyncFib task : tasks) assertTrue(task.isDone()); + for (AsyncFib task : tasks) task.checkCompletedNormally(); + }}; + testInvokeOnPool(pool, a); + } + + /** + * invokeAll(collection) invokes all tasks in the collection + */ + public void testInvokeAllCollection() { + testInvokeAllCollection(mainPool()); + } + public void testInvokeAllCollection_Singleton() { + testInvokeAllCollection(singletonPool()); + } + public void testInvokeAllCollection(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib[] tasks = { + new AsyncFib(8), + new AsyncFib(9), + new AsyncFib(7), + }; + invokeAll(Arrays.asList(tasks)); + for (AsyncFib task : tasks) assertTrue(task.isDone()); + for (AsyncFib task : tasks) task.checkCompletedNormally(); + }}; + testInvokeOnPool(pool, a); + } + + /** + * invokeAll(tasks) with any null task throws NullPointerException + */ + public void testInvokeAllNullTask() { + testInvokeAllNullTask(mainPool()); + } + public void testInvokeAllNullTask_Singleton() { + testInvokeAllNullTask(singletonPool()); + } + public void testInvokeAllNullTask(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib nul = null; + Runnable[] throwingActions = { + () -> invokeAll(nul), + () -> invokeAll(nul, nul), + () -> invokeAll(new AsyncFib(8), new AsyncFib(9), nul), + () -> invokeAll(new AsyncFib(8), nul, new AsyncFib(9)), + () -> invokeAll(nul, new AsyncFib(8), new AsyncFib(9)), + }; + assertThrows(NullPointerException.class, throwingActions); + }}; + testInvokeOnPool(pool, a); + } + + /** + * invokeAll(tasks) with 1 argument throws exception if task does + */ + public void testAbnormalInvokeAll1() { + testAbnormalInvokeAll1(mainPool()); + } + public void testAbnormalInvokeAll1_Singleton() { + testAbnormalInvokeAll1(singletonPool()); + } + public void testAbnormalInvokeAll1(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingAsyncFib g = new FailingAsyncFib(9); + try { + invokeAll(g); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + testInvokeOnPool(pool, a); + } + + /** + * invokeAll(t1, t2) throw exception if any task does + */ + public void testAbnormalInvokeAll2() { + testAbnormalInvokeAll2(mainPool()); + } + public void testAbnormalInvokeAll2_Singleton() { + testAbnormalInvokeAll2(singletonPool()); + } + public void testAbnormalInvokeAll2(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + FailingAsyncFib g = new FailingAsyncFib(9); + ForkJoinTask[] tasks = { f, g }; + Collections.shuffle(Arrays.asList(tasks)); + try { + invokeAll(tasks[0], tasks[1]); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + testInvokeOnPool(pool, a); + } + + /** + * invokeAll(tasks) with > 2 argument throws exception if any task does + */ + public void testAbnormalInvokeAll3() { + testAbnormalInvokeAll3(mainPool()); + } + public void testAbnormalInvokeAll3_Singleton() { + testAbnormalInvokeAll3(singletonPool()); + } + public void testAbnormalInvokeAll3(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + FailingAsyncFib g = new FailingAsyncFib(9); + AsyncFib h = new AsyncFib(7); + ForkJoinTask[] tasks = { f, g, h }; + Collections.shuffle(Arrays.asList(tasks)); + try { + invokeAll(tasks[0], tasks[1], tasks[2]); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + testInvokeOnPool(pool, a); + } + + /** + * invokeAll(collection) throws exception if any task does + */ + public void testAbnormalInvokeAllCollection() { + testAbnormalInvokeAllCollection(mainPool()); + } + public void testAbnormalInvokeAllCollection_Singleton() { + testAbnormalInvokeAllCollection(singletonPool()); + } + public void testAbnormalInvokeAllCollection(ForkJoinPool pool) { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingAsyncFib f = new FailingAsyncFib(8); + AsyncFib g = new AsyncFib(9); + AsyncFib h = new AsyncFib(7); + ForkJoinTask[] tasks = { f, g, h }; + Collections.shuffle(Arrays.asList(tasks)); + try { + invokeAll(Arrays.asList(tasks)); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(pool, a); + } + + /** + * tryUnfork returns true for most recent unexecuted task, + * and suppresses execution + */ + public void testTryUnfork() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib g = new AsyncFib(9); + assertSame(g, g.fork()); + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertTrue(f.tryUnfork()); + helpQuiesce(); + checkNotDone(f); + g.checkCompletedNormally(); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * getSurplusQueuedTaskCount returns > 0 when + * there are more tasks than threads + */ + public void testGetSurplusQueuedTaskCount() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib h = new AsyncFib(7); + assertSame(h, h.fork()); + AsyncFib g = new AsyncFib(9); + assertSame(g, g.fork()); + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertTrue(getSurplusQueuedTaskCount() > 0); + helpQuiesce(); + assertEquals(0, getSurplusQueuedTaskCount()); + f.checkCompletedNormally(); + g.checkCompletedNormally(); + h.checkCompletedNormally(); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * peekNextLocalTask returns most recent unexecuted task. + */ + public void testPeekNextLocalTask() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib g = new AsyncFib(9); + assertSame(g, g.fork()); + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertSame(f, peekNextLocalTask()); + assertNull(f.join()); + f.checkCompletedNormally(); + helpQuiesce(); + g.checkCompletedNormally(); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * pollNextLocalTask returns most recent unexecuted task without + * executing it + */ + public void testPollNextLocalTask() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib g = new AsyncFib(9); + assertSame(g, g.fork()); + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertSame(f, pollNextLocalTask()); + helpQuiesce(); + checkNotDone(f); + g.checkCompletedNormally(); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * pollTask returns an unexecuted task without executing it + */ + public void testPollTask() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib g = new AsyncFib(9); + assertSame(g, g.fork()); + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertSame(f, pollTask()); + helpQuiesce(); + checkNotDone(f); + g.checkCompletedNormally(); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * peekNextLocalTask returns least recent unexecuted task in async mode + */ + public void testPeekNextLocalTaskAsync() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib g = new AsyncFib(9); + assertSame(g, g.fork()); + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertSame(g, peekNextLocalTask()); + assertNull(f.join()); + helpQuiesce(); + f.checkCompletedNormally(); + g.checkCompletedNormally(); + }}; + testInvokeOnPool(asyncSingletonPool(), a); + } + + /** + * pollNextLocalTask returns least recent unexecuted task without + * executing it, in async mode + */ + public void testPollNextLocalTaskAsync() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib g = new AsyncFib(9); + assertSame(g, g.fork()); + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertSame(g, pollNextLocalTask()); + helpQuiesce(); + f.checkCompletedNormally(); + checkNotDone(g); + }}; + testInvokeOnPool(asyncSingletonPool(), a); + } + + /** + * pollTask returns an unexecuted task without executing it, in + * async mode + */ + public void testPollTaskAsync() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib g = new AsyncFib(9); + assertSame(g, g.fork()); + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertSame(g, pollTask()); + helpQuiesce(); + f.checkCompletedNormally(); + checkNotDone(g); + }}; + testInvokeOnPool(asyncSingletonPool(), a); + } + + /** + * ForkJoinTask.quietlyComplete returns when task completes + * normally without setting a value. The most recent value + * established by setRawResult(V) (or null by default) is returned + * from invoke. + */ + public void testQuietlyComplete() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + f.quietlyComplete(); + assertEquals(8, f.number); + assertTrue(f.isDone()); + assertFalse(f.isCancelled()); + assertTrue(f.isCompletedNormally()); + assertFalse(f.isCompletedAbnormally()); + assertNull(f.getException()); + }}; + testInvokeOnPool(mainPool(), a); + } + + // jdk9 + + /** + * pollSubmission returns unexecuted submitted task, if present + */ + public void testPollSubmission() { + final CountDownLatch done = new CountDownLatch(1); + final ForkJoinTask a = ForkJoinTask.adapt(awaiter(done)); + final ForkJoinTask b = ForkJoinTask.adapt(awaiter(done)); + final ForkJoinTask c = ForkJoinTask.adapt(awaiter(done)); + final ForkJoinPool p = singletonPool(); + try (PoolCleaner cleaner = cleaner(p, done)) { + Thread external = new Thread(new CheckedRunnable() { + public void realRun() { + p.execute(a); + p.execute(b); + p.execute(c); + }}); + RecursiveAction s = new CheckedRecursiveAction() { + protected void realCompute() { + external.start(); + try { + external.join(); + } catch (Exception ex) { + threadUnexpectedException(ex); + } + assertTrue(p.hasQueuedSubmissions()); + assertTrue(Thread.currentThread() instanceof ForkJoinWorkerThread); + ForkJoinTask r = ForkJoinTask.pollSubmission(); + assertTrue(r == a || r == b || r == c); + assertFalse(r.isDone()); + }}; + p.invoke(s); + } + } + +} diff --git a/jdk/test/java/util/concurrent/tck/ForkJoinTaskTest.java b/jdk/test/java/util/concurrent/tck/ForkJoinTaskTest.java new file mode 100644 index 00000000000..7000c1a4bb5 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ForkJoinTaskTest.java @@ -0,0 +1,1685 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinTask; +import java.util.concurrent.RecursiveAction; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ForkJoinTaskTest extends JSR166TestCase { + + public static void main(String[] args) { + main(suite(), args); + } + + public static Test suite() { + return new TestSuite(ForkJoinTaskTest.class); + } + + // Runs with "mainPool" use > 1 thread. singletonPool tests use 1 + static final int mainPoolSize = + Math.max(2, Runtime.getRuntime().availableProcessors()); + + private static ForkJoinPool mainPool() { + return new ForkJoinPool(mainPoolSize); + } + + private static ForkJoinPool singletonPool() { + return new ForkJoinPool(1); + } + + private static ForkJoinPool asyncSingletonPool() { + return new ForkJoinPool(1, + ForkJoinPool.defaultForkJoinWorkerThreadFactory, + null, true); + } + + private void testInvokeOnPool(ForkJoinPool pool, RecursiveAction a) { + try (PoolCleaner cleaner = cleaner(pool)) { + assertFalse(a.isDone()); + assertFalse(a.isCompletedNormally()); + assertFalse(a.isCompletedAbnormally()); + assertFalse(a.isCancelled()); + assertNull(a.getException()); + assertNull(a.getRawResult()); + + assertNull(pool.invoke(a)); + + assertTrue(a.isDone()); + assertTrue(a.isCompletedNormally()); + assertFalse(a.isCompletedAbnormally()); + assertFalse(a.isCancelled()); + assertNull(a.getException()); + assertNull(a.getRawResult()); + } + } + + void checkNotDone(ForkJoinTask a) { + assertFalse(a.isDone()); + assertFalse(a.isCompletedNormally()); + assertFalse(a.isCompletedAbnormally()); + assertFalse(a.isCancelled()); + assertNull(a.getException()); + assertNull(a.getRawResult()); + + try { + a.get(0L, SECONDS); + shouldThrow(); + } catch (TimeoutException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + void checkCompletedNormally(ForkJoinTask a) { + checkCompletedNormally(a, null); + } + + void checkCompletedNormally(ForkJoinTask a, T expected) { + assertTrue(a.isDone()); + assertFalse(a.isCancelled()); + assertTrue(a.isCompletedNormally()); + assertFalse(a.isCompletedAbnormally()); + assertNull(a.getException()); + assertSame(expected, a.getRawResult()); + + { + Thread.currentThread().interrupt(); + long startTime = System.nanoTime(); + assertSame(expected, a.join()); + assertTrue(millisElapsedSince(startTime) < SMALL_DELAY_MS); + Thread.interrupted(); + } + + { + Thread.currentThread().interrupt(); + long startTime = System.nanoTime(); + a.quietlyJoin(); // should be no-op + assertTrue(millisElapsedSince(startTime) < SMALL_DELAY_MS); + Thread.interrupted(); + } + + assertFalse(a.cancel(false)); + assertFalse(a.cancel(true)); + try { + assertSame(expected, a.get()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + try { + assertSame(expected, a.get(5L, SECONDS)); + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + void checkCancelled(ForkJoinTask a) { + assertTrue(a.isDone()); + assertTrue(a.isCancelled()); + assertFalse(a.isCompletedNormally()); + assertTrue(a.isCompletedAbnormally()); + assertTrue(a.getException() instanceof CancellationException); + assertNull(a.getRawResult()); + assertTrue(a.cancel(false)); + assertTrue(a.cancel(true)); + + try { + Thread.currentThread().interrupt(); + a.join(); + shouldThrow(); + } catch (CancellationException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + Thread.interrupted(); + + { + long startTime = System.nanoTime(); + a.quietlyJoin(); // should be no-op + assertTrue(millisElapsedSince(startTime) < SMALL_DELAY_MS); + } + + try { + a.get(); + shouldThrow(); + } catch (CancellationException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + + try { + a.get(5L, SECONDS); + shouldThrow(); + } catch (CancellationException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + void checkCompletedAbnormally(ForkJoinTask a, Throwable t) { + assertTrue(a.isDone()); + assertFalse(a.isCancelled()); + assertFalse(a.isCompletedNormally()); + assertTrue(a.isCompletedAbnormally()); + assertSame(t.getClass(), a.getException().getClass()); + assertNull(a.getRawResult()); + assertFalse(a.cancel(false)); + assertFalse(a.cancel(true)); + + try { + Thread.currentThread().interrupt(); + a.join(); + shouldThrow(); + } catch (Throwable expected) { + assertSame(t.getClass(), expected.getClass()); + } + Thread.interrupted(); + + { + long startTime = System.nanoTime(); + a.quietlyJoin(); // should be no-op + assertTrue(millisElapsedSince(startTime) < SMALL_DELAY_MS); + } + + try { + a.get(); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(t.getClass(), success.getCause().getClass()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + + try { + a.get(5L, SECONDS); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(t.getClass(), success.getCause().getClass()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + /* + * Testing coverage notes: + * + * To test extension methods and overrides, most tests use + * BinaryAsyncAction extension class that processes joins + * differently than supplied Recursive forms. + */ + + public static final class FJException extends RuntimeException { + FJException() { super(); } + } + + abstract static class BinaryAsyncAction extends ForkJoinTask { + private volatile int controlState; + + static final AtomicIntegerFieldUpdater controlStateUpdater = + AtomicIntegerFieldUpdater.newUpdater(BinaryAsyncAction.class, + "controlState"); + + private volatile BinaryAsyncAction parent; + + private volatile BinaryAsyncAction sibling; + + protected BinaryAsyncAction() { + } + + public final Void getRawResult() { return null; } + protected final void setRawResult(Void mustBeNull) { } + + public final void linkSubtasks(BinaryAsyncAction x, BinaryAsyncAction y) { + x.parent = y.parent = this; + x.sibling = y; + y.sibling = x; + } + + protected void onComplete(BinaryAsyncAction x, BinaryAsyncAction y) { + } + + protected boolean onException() { + return true; + } + + public void linkAndForkSubtasks(BinaryAsyncAction x, BinaryAsyncAction y) { + linkSubtasks(x, y); + y.fork(); + x.fork(); + } + + private void completeThis() { + super.complete(null); + } + + private void completeThisExceptionally(Throwable ex) { + super.completeExceptionally(ex); + } + + public boolean cancel(boolean mayInterruptIfRunning) { + if (super.cancel(mayInterruptIfRunning)) { + completeExceptionally(new FJException()); + return true; + } + return false; + } + + public final void complete() { + BinaryAsyncAction a = this; + for (;;) { + BinaryAsyncAction s = a.sibling; + BinaryAsyncAction p = a.parent; + a.sibling = null; + a.parent = null; + a.completeThis(); + if (p == null || p.compareAndSetControlState(0, 1)) + break; + try { + p.onComplete(a, s); + } catch (Throwable rex) { + p.completeExceptionally(rex); + return; + } + a = p; + } + } + + public final void completeExceptionally(Throwable ex) { + for (BinaryAsyncAction a = this;;) { + a.completeThisExceptionally(ex); + BinaryAsyncAction s = a.sibling; + if (s != null && !s.isDone()) + s.completeExceptionally(ex); + if ((a = a.parent) == null) + break; + } + } + + public final BinaryAsyncAction getParent() { + return parent; + } + + public BinaryAsyncAction getSibling() { + return sibling; + } + + public void reinitialize() { + parent = sibling = null; + super.reinitialize(); + } + + protected final int getControlState() { + return controlState; + } + + protected final boolean compareAndSetControlState(int expect, + int update) { + return controlStateUpdater.compareAndSet(this, expect, update); + } + + protected final void setControlState(int value) { + controlState = value; + } + + protected final void incrementControlState() { + controlStateUpdater.incrementAndGet(this); + } + + protected final void decrementControlState() { + controlStateUpdater.decrementAndGet(this); + } + + } + + static final class AsyncFib extends BinaryAsyncAction { + int number; + public AsyncFib(int n) { + this.number = n; + } + + public final boolean exec() { + AsyncFib f = this; + int n = f.number; + while (n > 1) { + AsyncFib p = f; + AsyncFib r = new AsyncFib(n - 2); + f = new AsyncFib(--n); + p.linkSubtasks(r, f); + r.fork(); + } + f.complete(); + return false; + } + + protected void onComplete(BinaryAsyncAction x, BinaryAsyncAction y) { + number = ((AsyncFib)x).number + ((AsyncFib)y).number; + } + } + + static final class FailingAsyncFib extends BinaryAsyncAction { + int number; + public FailingAsyncFib(int n) { + this.number = n; + } + + public final boolean exec() { + FailingAsyncFib f = this; + int n = f.number; + while (n > 1) { + FailingAsyncFib p = f; + FailingAsyncFib r = new FailingAsyncFib(n - 2); + f = new FailingAsyncFib(--n); + p.linkSubtasks(r, f); + r.fork(); + } + f.complete(); + return false; + } + + protected void onComplete(BinaryAsyncAction x, BinaryAsyncAction y) { + completeExceptionally(new FJException()); + } + } + + /** + * invoke returns when task completes normally. + * isCompletedAbnormally and isCancelled return false for normally + * completed tasks; getRawResult returns null. + */ + public void testInvoke() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + assertNull(f.invoke()); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * quietlyInvoke task returns when task completes normally. + * isCompletedAbnormally and isCancelled return false for normally + * completed tasks + */ + public void testQuietlyInvoke() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + f.quietlyInvoke(); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * join of a forked task returns when task completes + */ + public void testForkJoin() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertNull(f.join()); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * get of a forked task returns when task completes + */ + public void testForkGet() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertNull(f.get()); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * timed get of a forked task returns when task completes + */ + public void testForkTimedGet() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertNull(f.get(LONG_DELAY_MS, MILLISECONDS)); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * timed get with null time unit throws NPE + */ + public void testForkTimedGetNPE() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + try { + f.get(5L, null); + shouldThrow(); + } catch (NullPointerException success) {} + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * quietlyJoin of a forked task returns when task completes + */ + public void testForkQuietlyJoin() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + f.quietlyJoin(); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * helpQuiesce returns when tasks are complete. + * getQueuedTaskCount returns 0 when quiescent + */ + public void testForkHelpQuiesce() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + helpQuiesce(); + assertEquals(21, f.number); + assertEquals(0, getQueuedTaskCount()); + checkCompletedNormally(f); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invoke task throws exception when task completes abnormally + */ + public void testAbnormalInvoke() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingAsyncFib f = new FailingAsyncFib(8); + try { + f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * quietlyInvoke task returns when task completes abnormally + */ + public void testAbnormalQuietlyInvoke() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingAsyncFib f = new FailingAsyncFib(8); + f.quietlyInvoke(); + assertTrue(f.getException() instanceof FJException); + checkCompletedAbnormally(f, f.getException()); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * join of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkJoin() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingAsyncFib f = new FailingAsyncFib(8); + assertSame(f, f.fork()); + try { + f.join(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * get of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkGet() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FailingAsyncFib f = new FailingAsyncFib(8); + assertSame(f, f.fork()); + try { + f.get(); + shouldThrow(); + } catch (ExecutionException success) { + Throwable cause = success.getCause(); + assertTrue(cause instanceof FJException); + checkCompletedAbnormally(f, cause); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * timed get of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkTimedGet() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FailingAsyncFib f = new FailingAsyncFib(8); + assertSame(f, f.fork()); + try { + f.get(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (ExecutionException success) { + Throwable cause = success.getCause(); + assertTrue(cause instanceof FJException); + checkCompletedAbnormally(f, cause); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * quietlyJoin of a forked task returns when task completes abnormally + */ + public void testAbnormalForkQuietlyJoin() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingAsyncFib f = new FailingAsyncFib(8); + assertSame(f, f.fork()); + f.quietlyJoin(); + assertTrue(f.getException() instanceof FJException); + checkCompletedAbnormally(f, f.getException()); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invoke task throws exception when task cancelled + */ + public void testCancelledInvoke() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + assertTrue(f.cancel(true)); + try { + f.invoke(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * join of a forked task throws exception when task cancelled + */ + public void testCancelledForkJoin() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.join(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * get of a forked task throws exception when task cancelled + */ + public void testCancelledForkGet() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + AsyncFib f = new AsyncFib(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.get(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * timed get of a forked task throws exception when task cancelled + */ + public void testCancelledForkTimedGet() throws Exception { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + AsyncFib f = new AsyncFib(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.get(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * quietlyJoin of a forked task returns when task cancelled + */ + public void testCancelledForkQuietlyJoin() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + f.quietlyJoin(); + checkCancelled(f); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * getPool of executing task returns its pool + */ + public void testGetPool() { + final ForkJoinPool mainPool = mainPool(); + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + assertSame(mainPool, getPool()); + }}; + testInvokeOnPool(mainPool, a); + } + + /** + * getPool of non-FJ task returns null + */ + public void testGetPool2() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + assertNull(getPool()); + }}; + assertNull(a.invoke()); + } + + /** + * inForkJoinPool of executing task returns true + */ + public void testInForkJoinPool() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + assertTrue(inForkJoinPool()); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * inForkJoinPool of non-FJ task returns false + */ + public void testInForkJoinPool2() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + assertFalse(inForkJoinPool()); + }}; + assertNull(a.invoke()); + } + + /** + * setRawResult(null) succeeds + */ + public void testSetRawResult() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + setRawResult(null); + assertNull(getRawResult()); + }}; + assertNull(a.invoke()); + } + + /** + * invoke task throws exception after invoking completeExceptionally + */ + public void testCompleteExceptionally() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + f.completeExceptionally(new FJException()); + try { + f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * completeExceptionally(null) surprisingly has the same effect as + * completeExceptionally(new RuntimeException()) + */ + public void testCompleteExceptionally_null() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + f.completeExceptionally(null); + try { + f.invoke(); + shouldThrow(); + } catch (RuntimeException success) { + assertSame(success.getClass(), RuntimeException.class); + assertNull(success.getCause()); + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(t1, t2) invokes all task arguments + */ + public void testInvokeAll2() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + AsyncFib g = new AsyncFib(9); + invokeAll(f, g); + assertEquals(21, f.number); + assertEquals(34, g.number); + checkCompletedNormally(f); + checkCompletedNormally(g); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(tasks) with 1 argument invokes task + */ + public void testInvokeAll1() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + invokeAll(f); + checkCompletedNormally(f); + assertEquals(21, f.number); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(tasks) with > 2 argument invokes tasks + */ + public void testInvokeAll3() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + AsyncFib g = new AsyncFib(9); + AsyncFib h = new AsyncFib(7); + invokeAll(f, g, h); + assertEquals(21, f.number); + assertEquals(34, g.number); + assertEquals(13, h.number); + checkCompletedNormally(f); + checkCompletedNormally(g); + checkCompletedNormally(h); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(collection) invokes all tasks in the collection + */ + public void testInvokeAllCollection() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + AsyncFib g = new AsyncFib(9); + AsyncFib h = new AsyncFib(7); + HashSet set = new HashSet(); + set.add(f); + set.add(g); + set.add(h); + invokeAll(set); + assertEquals(21, f.number); + assertEquals(34, g.number); + assertEquals(13, h.number); + checkCompletedNormally(f); + checkCompletedNormally(g); + checkCompletedNormally(h); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(tasks) with any null task throws NPE + */ + public void testInvokeAllNPE() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + AsyncFib g = new AsyncFib(9); + AsyncFib h = null; + try { + invokeAll(f, g, h); + shouldThrow(); + } catch (NullPointerException success) {} + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(t1, t2) throw exception if any task does + */ + public void testAbnormalInvokeAll2() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + FailingAsyncFib g = new FailingAsyncFib(9); + ForkJoinTask[] tasks = { f, g }; + Collections.shuffle(Arrays.asList(tasks)); + try { + invokeAll(tasks); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(tasks) with 1 argument throws exception if task does + */ + public void testAbnormalInvokeAll1() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingAsyncFib g = new FailingAsyncFib(9); + try { + invokeAll(g); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(tasks) with > 2 argument throws exception if any task does + */ + public void testAbnormalInvokeAll3() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + FailingAsyncFib g = new FailingAsyncFib(9); + AsyncFib h = new AsyncFib(7); + ForkJoinTask[] tasks = { f, g, h }; + Collections.shuffle(Arrays.asList(tasks)); + try { + invokeAll(tasks); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(collection) throws exception if any task does + */ + public void testAbnormalInvokeAllCollection() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingAsyncFib f = new FailingAsyncFib(8); + AsyncFib g = new AsyncFib(9); + AsyncFib h = new AsyncFib(7); + ForkJoinTask[] tasks = { f, g, h }; + List taskList = Arrays.asList(tasks); + Collections.shuffle(taskList); + try { + invokeAll(taskList); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * tryUnfork returns true for most recent unexecuted task, + * and suppresses execution + */ + public void testTryUnfork() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib g = new AsyncFib(9); + assertSame(g, g.fork()); + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertTrue(f.tryUnfork()); + helpQuiesce(); + checkNotDone(f); + checkCompletedNormally(g); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * getSurplusQueuedTaskCount returns > 0 when + * there are more tasks than threads + */ + public void testGetSurplusQueuedTaskCount() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib h = new AsyncFib(7); + assertSame(h, h.fork()); + AsyncFib g = new AsyncFib(9); + assertSame(g, g.fork()); + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertTrue(getSurplusQueuedTaskCount() > 0); + helpQuiesce(); + assertEquals(0, getSurplusQueuedTaskCount()); + checkCompletedNormally(f); + checkCompletedNormally(g); + checkCompletedNormally(h); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * peekNextLocalTask returns most recent unexecuted task. + */ + public void testPeekNextLocalTask() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib g = new AsyncFib(9); + assertSame(g, g.fork()); + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertSame(f, peekNextLocalTask()); + assertNull(f.join()); + checkCompletedNormally(f); + helpQuiesce(); + checkCompletedNormally(g); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * pollNextLocalTask returns most recent unexecuted task without + * executing it + */ + public void testPollNextLocalTask() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib g = new AsyncFib(9); + assertSame(g, g.fork()); + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertSame(f, pollNextLocalTask()); + helpQuiesce(); + checkNotDone(f); + assertEquals(34, g.number); + checkCompletedNormally(g); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * pollTask returns an unexecuted task without executing it + */ + public void testPollTask() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib g = new AsyncFib(9); + assertSame(g, g.fork()); + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertSame(f, pollTask()); + helpQuiesce(); + checkNotDone(f); + checkCompletedNormally(g); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * peekNextLocalTask returns least recent unexecuted task in async mode + */ + public void testPeekNextLocalTaskAsync() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib g = new AsyncFib(9); + assertSame(g, g.fork()); + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertSame(g, peekNextLocalTask()); + assertNull(f.join()); + helpQuiesce(); + checkCompletedNormally(f); + assertEquals(34, g.number); + checkCompletedNormally(g); + }}; + testInvokeOnPool(asyncSingletonPool(), a); + } + + /** + * pollNextLocalTask returns least recent unexecuted task without + * executing it, in async mode + */ + public void testPollNextLocalTaskAsync() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib g = new AsyncFib(9); + assertSame(g, g.fork()); + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertSame(g, pollNextLocalTask()); + helpQuiesce(); + assertEquals(21, f.number); + checkCompletedNormally(f); + checkNotDone(g); + }}; + testInvokeOnPool(asyncSingletonPool(), a); + } + + /** + * pollTask returns an unexecuted task without executing it, in + * async mode + */ + public void testPollTaskAsync() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib g = new AsyncFib(9); + assertSame(g, g.fork()); + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertSame(g, pollTask()); + helpQuiesce(); + assertEquals(21, f.number); + checkCompletedNormally(f); + checkNotDone(g); + }}; + testInvokeOnPool(asyncSingletonPool(), a); + } + + // versions for singleton pools + + /** + * invoke returns when task completes normally. + * isCompletedAbnormally and isCancelled return false for normally + * completed tasks; getRawResult returns null. + */ + public void testInvokeSingleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + assertNull(f.invoke()); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * quietlyInvoke task returns when task completes normally. + * isCompletedAbnormally and isCancelled return false for normally + * completed tasks + */ + public void testQuietlyInvokeSingleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + f.quietlyInvoke(); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * join of a forked task returns when task completes + */ + public void testForkJoinSingleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertNull(f.join()); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * get of a forked task returns when task completes + */ + public void testForkGetSingleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertNull(f.get()); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * timed get of a forked task returns when task completes + */ + public void testForkTimedGetSingleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + assertNull(f.get(LONG_DELAY_MS, MILLISECONDS)); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * timed get with null time unit throws NPE + */ + public void testForkTimedGetNPESingleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + try { + f.get(5L, null); + shouldThrow(); + } catch (NullPointerException success) {} + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * quietlyJoin of a forked task returns when task completes + */ + public void testForkQuietlyJoinSingleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + f.quietlyJoin(); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * helpQuiesce returns when tasks are complete. + * getQueuedTaskCount returns 0 when quiescent + */ + public void testForkHelpQuiesceSingleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + assertSame(f, f.fork()); + helpQuiesce(); + assertEquals(0, getQueuedTaskCount()); + assertEquals(21, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * invoke task throws exception when task completes abnormally + */ + public void testAbnormalInvokeSingleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingAsyncFib f = new FailingAsyncFib(8); + try { + f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * quietlyInvoke task returns when task completes abnormally + */ + public void testAbnormalQuietlyInvokeSingleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingAsyncFib f = new FailingAsyncFib(8); + f.quietlyInvoke(); + assertTrue(f.getException() instanceof FJException); + checkCompletedAbnormally(f, f.getException()); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * join of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkJoinSingleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingAsyncFib f = new FailingAsyncFib(8); + assertSame(f, f.fork()); + try { + f.join(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * get of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkGetSingleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FailingAsyncFib f = new FailingAsyncFib(8); + assertSame(f, f.fork()); + try { + f.get(); + shouldThrow(); + } catch (ExecutionException success) { + Throwable cause = success.getCause(); + assertTrue(cause instanceof FJException); + checkCompletedAbnormally(f, cause); + } + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * timed get of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkTimedGetSingleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FailingAsyncFib f = new FailingAsyncFib(8); + assertSame(f, f.fork()); + try { + f.get(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (ExecutionException success) { + Throwable cause = success.getCause(); + assertTrue(cause instanceof FJException); + checkCompletedAbnormally(f, cause); + } + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * quietlyJoin of a forked task returns when task completes abnormally + */ + public void testAbnormalForkQuietlyJoinSingleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingAsyncFib f = new FailingAsyncFib(8); + assertSame(f, f.fork()); + f.quietlyJoin(); + assertTrue(f.getException() instanceof FJException); + checkCompletedAbnormally(f, f.getException()); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * invoke task throws exception when task cancelled + */ + public void testCancelledInvokeSingleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + assertTrue(f.cancel(true)); + try { + f.invoke(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * join of a forked task throws exception when task cancelled + */ + public void testCancelledForkJoinSingleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.join(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * get of a forked task throws exception when task cancelled + */ + public void testCancelledForkGetSingleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + AsyncFib f = new AsyncFib(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.get(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * timed get of a forked task throws exception when task cancelled + */ + public void testCancelledForkTimedGetSingleton() throws Exception { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + AsyncFib f = new AsyncFib(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.get(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * quietlyJoin of a forked task returns when task cancelled + */ + public void testCancelledForkQuietlyJoinSingleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + f.quietlyJoin(); + checkCancelled(f); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * invoke task throws exception after invoking completeExceptionally + */ + public void testCompleteExceptionallySingleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + f.completeExceptionally(new FJException()); + try { + f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * invokeAll(t1, t2) invokes all task arguments + */ + public void testInvokeAll2Singleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + AsyncFib g = new AsyncFib(9); + invokeAll(f, g); + assertEquals(21, f.number); + assertEquals(34, g.number); + checkCompletedNormally(f); + checkCompletedNormally(g); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * invokeAll(tasks) with 1 argument invokes task + */ + public void testInvokeAll1Singleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + invokeAll(f); + checkCompletedNormally(f); + assertEquals(21, f.number); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * invokeAll(tasks) with > 2 argument invokes tasks + */ + public void testInvokeAll3Singleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + AsyncFib g = new AsyncFib(9); + AsyncFib h = new AsyncFib(7); + invokeAll(f, g, h); + assertEquals(21, f.number); + assertEquals(34, g.number); + assertEquals(13, h.number); + checkCompletedNormally(f); + checkCompletedNormally(g); + checkCompletedNormally(h); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * invokeAll(collection) invokes all tasks in the collection + */ + public void testInvokeAllCollectionSingleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + AsyncFib g = new AsyncFib(9); + AsyncFib h = new AsyncFib(7); + HashSet set = new HashSet(); + set.add(f); + set.add(g); + set.add(h); + invokeAll(set); + assertEquals(21, f.number); + assertEquals(34, g.number); + assertEquals(13, h.number); + checkCompletedNormally(f); + checkCompletedNormally(g); + checkCompletedNormally(h); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * invokeAll(tasks) with any null task throws NPE + */ + public void testInvokeAllNPESingleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + AsyncFib g = new AsyncFib(9); + AsyncFib h = null; + try { + invokeAll(f, g, h); + shouldThrow(); + } catch (NullPointerException success) {} + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * invokeAll(t1, t2) throw exception if any task does + */ + public void testAbnormalInvokeAll2Singleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + FailingAsyncFib g = new FailingAsyncFib(9); + ForkJoinTask[] tasks = { f, g }; + Collections.shuffle(Arrays.asList(tasks)); + try { + invokeAll(tasks); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * invokeAll(tasks) with 1 argument throws exception if task does + */ + public void testAbnormalInvokeAll1Singleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingAsyncFib g = new FailingAsyncFib(9); + try { + invokeAll(g); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * invokeAll(tasks) with > 2 argument throws exception if any task does + */ + public void testAbnormalInvokeAll3Singleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + FailingAsyncFib g = new FailingAsyncFib(9); + AsyncFib h = new AsyncFib(7); + ForkJoinTask[] tasks = { f, g, h }; + Collections.shuffle(Arrays.asList(tasks)); + try { + invokeAll(tasks); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * invokeAll(collection) throws exception if any task does + */ + public void testAbnormalInvokeAllCollectionSingleton() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingAsyncFib f = new FailingAsyncFib(8); + AsyncFib g = new AsyncFib(9); + AsyncFib h = new AsyncFib(7); + ForkJoinTask[] tasks = { f, g, h }; + List taskList = Arrays.asList(tasks); + Collections.shuffle(taskList); + try { + invokeAll(taskList); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * ForkJoinTask.quietlyComplete returns when task completes + * normally without setting a value. The most recent value + * established by setRawResult(V) (or null by default) is returned + * from invoke. + */ + public void testQuietlyComplete() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + AsyncFib f = new AsyncFib(8); + f.quietlyComplete(); + assertEquals(8, f.number); + checkCompletedNormally(f); + }}; + testInvokeOnPool(mainPool(), a); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/FutureTaskTest.java b/jdk/test/java/util/concurrent/tck/FutureTaskTest.java new file mode 100644 index 00000000000..1e858bfdbc1 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/FutureTaskTest.java @@ -0,0 +1,866 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; + +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class FutureTaskTest extends JSR166TestCase { + + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(FutureTaskTest.class); + } + + void checkIsDone(Future f) { + assertTrue(f.isDone()); + assertFalse(f.cancel(false)); + assertFalse(f.cancel(true)); + if (f instanceof PublicFutureTask) { + PublicFutureTask pf = (PublicFutureTask) f; + assertEquals(1, pf.doneCount()); + assertFalse(pf.runAndReset()); + assertEquals(1, pf.doneCount()); + Object r = null; Object exInfo = null; + try { + r = f.get(); + } catch (CancellationException t) { + exInfo = CancellationException.class; + } catch (ExecutionException t) { + exInfo = t.getCause(); + } catch (Throwable t) { + threadUnexpectedException(t); + } + + // Check that run and runAndReset have no effect. + int savedRunCount = pf.runCount(); + pf.run(); + pf.runAndReset(); + assertEquals(savedRunCount, pf.runCount()); + try { + assertSame(r, f.get()); + } catch (CancellationException t) { + assertSame(exInfo, CancellationException.class); + } catch (ExecutionException t) { + assertSame(exInfo, t.getCause()); + } catch (Throwable t) { + threadUnexpectedException(t); + } + assertTrue(f.isDone()); + } + } + + void checkNotDone(Future f) { + assertFalse(f.isDone()); + assertFalse(f.isCancelled()); + if (f instanceof PublicFutureTask) { + PublicFutureTask pf = (PublicFutureTask) f; + assertEquals(0, pf.doneCount()); + assertEquals(0, pf.setCount()); + assertEquals(0, pf.setExceptionCount()); + } + } + + void checkIsRunning(Future f) { + checkNotDone(f); + if (f instanceof FutureTask) { + FutureTask ft = (FutureTask) f; + // Check that run methods do nothing + ft.run(); + if (f instanceof PublicFutureTask) { + PublicFutureTask pf = (PublicFutureTask) f; + int savedRunCount = pf.runCount(); + pf.run(); + assertFalse(pf.runAndReset()); + assertEquals(savedRunCount, pf.runCount()); + } + checkNotDone(f); + } + } + + void checkCompletedNormally(Future f, T expected) { + checkIsDone(f); + assertFalse(f.isCancelled()); + + try { + assertSame(expected, f.get()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + try { + assertSame(expected, f.get(5L, SECONDS)); + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + void checkCancelled(Future f) { + checkIsDone(f); + assertTrue(f.isCancelled()); + + try { + f.get(); + shouldThrow(); + } catch (CancellationException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + + try { + f.get(5L, SECONDS); + shouldThrow(); + } catch (CancellationException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + void tryToConfuseDoneTask(PublicFutureTask pf) { + pf.set(new Object()); + pf.setException(new Error()); + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) { + pf.cancel(mayInterruptIfRunning); + } + } + + void checkCompletedAbnormally(Future f, Throwable t) { + checkIsDone(f); + assertFalse(f.isCancelled()); + + try { + f.get(); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(t, success.getCause()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + + try { + f.get(5L, SECONDS); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(t, success.getCause()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + /** + * Subclass to expose protected methods + */ + static class PublicFutureTask extends FutureTask { + private final AtomicInteger runCount; + private final AtomicInteger doneCount = new AtomicInteger(0); + private final AtomicInteger runAndResetCount = new AtomicInteger(0); + private final AtomicInteger setCount = new AtomicInteger(0); + private final AtomicInteger setExceptionCount = new AtomicInteger(0); + public int runCount() { return runCount.get(); } + public int doneCount() { return doneCount.get(); } + public int runAndResetCount() { return runAndResetCount.get(); } + public int setCount() { return setCount.get(); } + public int setExceptionCount() { return setExceptionCount.get(); } + + PublicFutureTask(Runnable runnable) { + this(runnable, seven); + } + PublicFutureTask(Runnable runnable, Object result) { + this(runnable, result, new AtomicInteger(0)); + } + private PublicFutureTask(final Runnable runnable, Object result, + final AtomicInteger runCount) { + super(new Runnable() { + public void run() { + runCount.getAndIncrement(); + runnable.run(); + }}, result); + this.runCount = runCount; + } + PublicFutureTask(Callable callable) { + this(callable, new AtomicInteger(0)); + } + private PublicFutureTask(final Callable callable, + final AtomicInteger runCount) { + super(new Callable() { + public Object call() throws Exception { + runCount.getAndIncrement(); + return callable.call(); + }}); + this.runCount = runCount; + } + @Override public void done() { + assertTrue(isDone()); + doneCount.incrementAndGet(); + super.done(); + } + @Override public boolean runAndReset() { + runAndResetCount.incrementAndGet(); + return super.runAndReset(); + } + @Override public void set(Object x) { + setCount.incrementAndGet(); + super.set(x); + } + @Override public void setException(Throwable t) { + setExceptionCount.incrementAndGet(); + super.setException(t); + } + } + + class Counter extends CheckedRunnable { + final AtomicInteger count = new AtomicInteger(0); + public int get() { return count.get(); } + public void realRun() { + count.getAndIncrement(); + } + } + + /** + * creating a future with a null callable throws NullPointerException + */ + public void testConstructor() { + try { + new FutureTask(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * creating a future with null runnable throws NullPointerException + */ + public void testConstructor2() { + try { + new FutureTask(null, Boolean.TRUE); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * isDone is true when a task completes + */ + public void testIsDone() { + PublicFutureTask task = new PublicFutureTask(new NoOpCallable()); + assertFalse(task.isDone()); + task.run(); + assertTrue(task.isDone()); + checkCompletedNormally(task, Boolean.TRUE); + assertEquals(1, task.runCount()); + } + + /** + * runAndReset of a non-cancelled task succeeds + */ + public void testRunAndReset() { + PublicFutureTask task = new PublicFutureTask(new NoOpCallable()); + for (int i = 0; i < 3; i++) { + assertTrue(task.runAndReset()); + checkNotDone(task); + assertEquals(i + 1, task.runCount()); + assertEquals(i + 1, task.runAndResetCount()); + assertEquals(0, task.setCount()); + assertEquals(0, task.setExceptionCount()); + } + } + + /** + * runAndReset after cancellation fails + */ + public void testRunAndResetAfterCancel() { + for (boolean mayInterruptIfRunning : new boolean[] { true, false }) { + PublicFutureTask task = new PublicFutureTask(new NoOpCallable()); + assertTrue(task.cancel(mayInterruptIfRunning)); + for (int i = 0; i < 3; i++) { + assertFalse(task.runAndReset()); + assertEquals(0, task.runCount()); + assertEquals(i + 1, task.runAndResetCount()); + assertEquals(0, task.setCount()); + assertEquals(0, task.setExceptionCount()); + } + tryToConfuseDoneTask(task); + checkCancelled(task); + } + } + + /** + * setting value causes get to return it + */ + public void testSet() throws Exception { + PublicFutureTask task = new PublicFutureTask(new NoOpCallable()); + task.set(one); + for (int i = 0; i < 3; i++) { + assertSame(one, task.get()); + assertSame(one, task.get(LONG_DELAY_MS, MILLISECONDS)); + assertEquals(1, task.setCount()); + } + tryToConfuseDoneTask(task); + checkCompletedNormally(task, one); + assertEquals(0, task.runCount()); + } + + /** + * setException causes get to throw ExecutionException + */ + public void testSetException_get() throws Exception { + Exception nse = new NoSuchElementException(); + PublicFutureTask task = new PublicFutureTask(new NoOpCallable()); + task.setException(nse); + + try { + task.get(); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(nse, success.getCause()); + checkCompletedAbnormally(task, nse); + } + + try { + task.get(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(nse, success.getCause()); + checkCompletedAbnormally(task, nse); + } + + assertEquals(1, task.setExceptionCount()); + assertEquals(0, task.setCount()); + tryToConfuseDoneTask(task); + checkCompletedAbnormally(task, nse); + assertEquals(0, task.runCount()); + } + + /** + * cancel(false) before run succeeds + */ + public void testCancelBeforeRun() { + PublicFutureTask task = new PublicFutureTask(new NoOpCallable()); + assertTrue(task.cancel(false)); + task.run(); + assertEquals(0, task.runCount()); + assertEquals(0, task.setCount()); + assertEquals(0, task.setExceptionCount()); + assertTrue(task.isCancelled()); + assertTrue(task.isDone()); + tryToConfuseDoneTask(task); + assertEquals(0, task.runCount()); + checkCancelled(task); + } + + /** + * cancel(true) before run succeeds + */ + public void testCancelBeforeRun2() { + PublicFutureTask task = new PublicFutureTask(new NoOpCallable()); + assertTrue(task.cancel(true)); + task.run(); + assertEquals(0, task.runCount()); + assertEquals(0, task.setCount()); + assertEquals(0, task.setExceptionCount()); + assertTrue(task.isCancelled()); + assertTrue(task.isDone()); + tryToConfuseDoneTask(task); + assertEquals(0, task.runCount()); + checkCancelled(task); + } + + /** + * cancel(false) of a completed task fails + */ + public void testCancelAfterRun() { + PublicFutureTask task = new PublicFutureTask(new NoOpCallable()); + task.run(); + assertFalse(task.cancel(false)); + assertEquals(1, task.runCount()); + assertEquals(1, task.setCount()); + assertEquals(0, task.setExceptionCount()); + tryToConfuseDoneTask(task); + checkCompletedNormally(task, Boolean.TRUE); + assertEquals(1, task.runCount()); + } + + /** + * cancel(true) of a completed task fails + */ + public void testCancelAfterRun2() { + PublicFutureTask task = new PublicFutureTask(new NoOpCallable()); + task.run(); + assertFalse(task.cancel(true)); + assertEquals(1, task.runCount()); + assertEquals(1, task.setCount()); + assertEquals(0, task.setExceptionCount()); + tryToConfuseDoneTask(task); + checkCompletedNormally(task, Boolean.TRUE); + assertEquals(1, task.runCount()); + } + + /** + * cancel(true) interrupts a running task that subsequently succeeds + */ + public void testCancelInterrupt() { + final CountDownLatch pleaseCancel = new CountDownLatch(1); + final PublicFutureTask task = + new PublicFutureTask(new CheckedRunnable() { + public void realRun() { + pleaseCancel.countDown(); + try { + delay(LONG_DELAY_MS); + shouldThrow(); + } catch (InterruptedException success) {} + }}); + + Thread t = newStartedThread(task); + await(pleaseCancel); + assertTrue(task.cancel(true)); + assertTrue(task.isCancelled()); + assertTrue(task.isDone()); + awaitTermination(t); + assertEquals(1, task.runCount()); + assertEquals(1, task.setCount()); + assertEquals(0, task.setExceptionCount()); + tryToConfuseDoneTask(task); + checkCancelled(task); + } + + /** + * cancel(true) tries to interrupt a running task, but + * Thread.interrupt throws (simulating a restrictive security + * manager) + */ + public void testCancelInterrupt_ThrowsSecurityException() { + final CountDownLatch pleaseCancel = new CountDownLatch(1); + final CountDownLatch cancelled = new CountDownLatch(1); + final PublicFutureTask task = + new PublicFutureTask(new CheckedRunnable() { + public void realRun() { + pleaseCancel.countDown(); + await(cancelled); + assertFalse(Thread.interrupted()); + }}); + + final Thread t = new Thread(task) { + // Simulate a restrictive security manager. + @Override public void interrupt() { + throw new SecurityException(); + }}; + t.setDaemon(true); + t.start(); + + await(pleaseCancel); + try { + task.cancel(true); + shouldThrow(); + } catch (SecurityException expected) {} + + // We failed to deliver the interrupt, but the world retains + // its sanity, as if we had done task.cancel(false) + assertTrue(task.isCancelled()); + assertTrue(task.isDone()); + assertEquals(1, task.runCount()); + assertEquals(1, task.doneCount()); + assertEquals(0, task.setCount()); + assertEquals(0, task.setExceptionCount()); + cancelled.countDown(); + awaitTermination(t); + assertEquals(1, task.setCount()); + assertEquals(0, task.setExceptionCount()); + tryToConfuseDoneTask(task); + checkCancelled(task); + } + + /** + * cancel(true) interrupts a running task that subsequently throws + */ + public void testCancelInterrupt_taskFails() { + final CountDownLatch pleaseCancel = new CountDownLatch(1); + final PublicFutureTask task = + new PublicFutureTask(new Runnable() { + public void run() { + pleaseCancel.countDown(); + try { + delay(LONG_DELAY_MS); + threadShouldThrow(); + } catch (InterruptedException success) { + } catch (Throwable t) { threadUnexpectedException(t); } + throw new RuntimeException(); + }}); + + Thread t = newStartedThread(task); + await(pleaseCancel); + assertTrue(task.cancel(true)); + assertTrue(task.isCancelled()); + awaitTermination(t); + assertEquals(1, task.runCount()); + assertEquals(0, task.setCount()); + assertEquals(1, task.setExceptionCount()); + tryToConfuseDoneTask(task); + checkCancelled(task); + } + + /** + * cancel(false) does not interrupt a running task + */ + public void testCancelNoInterrupt() { + final CountDownLatch pleaseCancel = new CountDownLatch(1); + final CountDownLatch cancelled = new CountDownLatch(1); + final PublicFutureTask task = + new PublicFutureTask(new CheckedCallable() { + public Boolean realCall() { + pleaseCancel.countDown(); + await(cancelled); + assertFalse(Thread.interrupted()); + return Boolean.TRUE; + }}); + + Thread t = newStartedThread(task); + await(pleaseCancel); + assertTrue(task.cancel(false)); + assertTrue(task.isCancelled()); + cancelled.countDown(); + awaitTermination(t); + assertEquals(1, task.runCount()); + assertEquals(1, task.setCount()); + assertEquals(0, task.setExceptionCount()); + tryToConfuseDoneTask(task); + checkCancelled(task); + } + + /** + * run in one thread causes get in another thread to retrieve value + */ + public void testGetRun() { + final CountDownLatch pleaseRun = new CountDownLatch(2); + + final PublicFutureTask task = + new PublicFutureTask(new CheckedCallable() { + public Object realCall() { + return two; + }}); + + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() throws Exception { + pleaseRun.countDown(); + assertSame(two, task.get()); + }}); + + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() throws Exception { + pleaseRun.countDown(); + assertSame(two, task.get(2*LONG_DELAY_MS, MILLISECONDS)); + }}); + + await(pleaseRun); + checkNotDone(task); + assertTrue(t1.isAlive()); + assertTrue(t2.isAlive()); + task.run(); + checkCompletedNormally(task, two); + assertEquals(1, task.runCount()); + assertEquals(1, task.setCount()); + assertEquals(0, task.setExceptionCount()); + awaitTermination(t1); + awaitTermination(t2); + tryToConfuseDoneTask(task); + checkCompletedNormally(task, two); + } + + /** + * set in one thread causes get in another thread to retrieve value + */ + public void testGetSet() { + final CountDownLatch pleaseSet = new CountDownLatch(2); + + final PublicFutureTask task = + new PublicFutureTask(new CheckedCallable() { + public Object realCall() throws InterruptedException { + return two; + }}); + + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() throws Exception { + pleaseSet.countDown(); + assertSame(two, task.get()); + }}); + + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() throws Exception { + pleaseSet.countDown(); + assertSame(two, task.get(2*LONG_DELAY_MS, MILLISECONDS)); + }}); + + await(pleaseSet); + checkNotDone(task); + assertTrue(t1.isAlive()); + assertTrue(t2.isAlive()); + task.set(two); + assertEquals(0, task.runCount()); + assertEquals(1, task.setCount()); + assertEquals(0, task.setExceptionCount()); + tryToConfuseDoneTask(task); + checkCompletedNormally(task, two); + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * Cancelling a task causes timed get in another thread to throw + * CancellationException + */ + public void testTimedGet_Cancellation() { + testTimedGet_Cancellation(false); + } + public void testTimedGet_Cancellation_interrupt() { + testTimedGet_Cancellation(true); + } + public void testTimedGet_Cancellation(final boolean mayInterruptIfRunning) { + final CountDownLatch pleaseCancel = new CountDownLatch(3); + final CountDownLatch cancelled = new CountDownLatch(1); + final Callable callable = + new CheckedCallable() { + public Object realCall() throws InterruptedException { + pleaseCancel.countDown(); + if (mayInterruptIfRunning) { + try { + delay(2*LONG_DELAY_MS); + } catch (InterruptedException success) {} + } else { + await(cancelled); + } + return two; + }}; + final PublicFutureTask task = new PublicFutureTask(callable); + + Thread t1 = new ThreadShouldThrow(CancellationException.class) { + public void realRun() throws Exception { + pleaseCancel.countDown(); + task.get(); + }}; + Thread t2 = new ThreadShouldThrow(CancellationException.class) { + public void realRun() throws Exception { + pleaseCancel.countDown(); + task.get(2*LONG_DELAY_MS, MILLISECONDS); + }}; + t1.start(); + t2.start(); + Thread t3 = newStartedThread(task); + await(pleaseCancel); + checkIsRunning(task); + task.cancel(mayInterruptIfRunning); + checkCancelled(task); + awaitTermination(t1); + awaitTermination(t2); + cancelled.countDown(); + awaitTermination(t3); + assertEquals(1, task.runCount()); + assertEquals(1, task.setCount()); + assertEquals(0, task.setExceptionCount()); + tryToConfuseDoneTask(task); + checkCancelled(task); + } + + /** + * A runtime exception in task causes get to throw ExecutionException + */ + public void testGet_ExecutionException() throws InterruptedException { + final ArithmeticException e = new ArithmeticException(); + final PublicFutureTask task = new PublicFutureTask(new Callable() { + public Object call() { + throw e; + }}); + + task.run(); + assertEquals(1, task.runCount()); + assertEquals(0, task.setCount()); + assertEquals(1, task.setExceptionCount()); + try { + task.get(); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(e, success.getCause()); + tryToConfuseDoneTask(task); + checkCompletedAbnormally(task, success.getCause()); + } + } + + /** + * A runtime exception in task causes timed get to throw ExecutionException + */ + public void testTimedGet_ExecutionException2() throws Exception { + final ArithmeticException e = new ArithmeticException(); + final PublicFutureTask task = new PublicFutureTask(new Callable() { + public Object call() { + throw e; + }}); + + task.run(); + try { + task.get(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(e, success.getCause()); + tryToConfuseDoneTask(task); + checkCompletedAbnormally(task, success.getCause()); + } + } + + /** + * get is interruptible + */ + public void testGet_interruptible() { + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + final FutureTask task = new FutureTask(new NoOpCallable()); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws Exception { + Thread.currentThread().interrupt(); + try { + task.get(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + task.get(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseInterrupt); + t.interrupt(); + awaitTermination(t); + checkNotDone(task); + } + + /** + * timed get is interruptible + */ + public void testTimedGet_interruptible() { + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + final FutureTask task = new FutureTask(new NoOpCallable()); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws Exception { + Thread.currentThread().interrupt(); + try { + task.get(2*LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + task.get(2*LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseInterrupt); + t.interrupt(); + awaitTermination(t); + checkNotDone(task); + } + + /** + * A timed out timed get throws TimeoutException + */ + public void testGet_TimeoutException() throws Exception { + FutureTask task = new FutureTask(new NoOpCallable()); + long startTime = System.nanoTime(); + try { + task.get(timeoutMillis(), MILLISECONDS); + shouldThrow(); + } catch (TimeoutException success) { + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + } + } + + /** + * timed get with null TimeUnit throws NullPointerException + */ + public void testGet_NullTimeUnit() throws Exception { + FutureTask task = new FutureTask(new NoOpCallable()); + long[] timeouts = { Long.MIN_VALUE, 0L, Long.MAX_VALUE }; + + for (long timeout : timeouts) { + try { + task.get(timeout, null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + task.run(); + + for (long timeout : timeouts) { + try { + task.get(timeout, null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed get with most negative timeout works correctly (i.e. no + * underflow bug) + */ + public void testGet_NegativeInfinityTimeout() throws Exception { + final ExecutorService pool = Executors.newFixedThreadPool(10); + final Runnable nop = new Runnable() { public void run() {}}; + final FutureTask task = new FutureTask<>(nop, null); + final List> futures = new ArrayList<>(); + Runnable r = new Runnable() { public void run() { + for (long timeout : new long[] { 0L, -1L, Long.MIN_VALUE }) { + try { + task.get(timeout, NANOSECONDS); + shouldThrow(); + } catch (TimeoutException success) { + } catch (Throwable fail) {threadUnexpectedException(fail);}}}}; + for (int i = 0; i < 10; i++) + futures.add(pool.submit(r)); + try { + joinPool(pool); + for (Future future : futures) + checkCompletedNormally(future, null); + } finally { + task.run(); // last resort to help terminate + } + } + +} diff --git a/jdk/test/java/util/concurrent/tck/JSR166TestCase.java b/jdk/test/java/util/concurrent/tck/JSR166TestCase.java new file mode 100644 index 00000000000..8063047f8b4 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/JSR166TestCase.java @@ -0,0 +1,1814 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +/* + * @test + * @summary JSR-166 tck tests + * @build * + * @run junit/othervm/timeout=1000 -Djsr166.testImplementationDetails=true JSR166TestCase + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.security.Policy; +import java.security.ProtectionDomain; +import java.security.SecurityPermission; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.PropertyPermission; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.Future; +import java.util.concurrent.RecursiveAction; +import java.util.concurrent.RecursiveTask; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.Semaphore; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestResult; +import junit.framework.TestSuite; + +/** + * Base class for JSR166 Junit TCK tests. Defines some constants, + * utility methods and classes, as well as a simple framework for + * helping to make sure that assertions failing in generated threads + * cause the associated test that generated them to itself fail (which + * JUnit does not otherwise arrange). The rules for creating such + * tests are: + * + *
    + * + *
  1. All assertions in code running in generated threads must use + * the forms {@link #threadFail}, {@link #threadAssertTrue}, {@link + * #threadAssertEquals}, or {@link #threadAssertNull}, (not + * {@code fail}, {@code assertTrue}, etc.) It is OK (but not + * particularly recommended) for other code to use these forms too. + * Only the most typically used JUnit assertion methods are defined + * this way, but enough to live with. + * + *
  2. If you override {@link #setUp} or {@link #tearDown}, make sure + * to invoke {@code super.setUp} and {@code super.tearDown} within + * them. These methods are used to clear and check for thread + * assertion failures. + * + *
  3. All delays and timeouts must use one of the constants {@code + * SHORT_DELAY_MS}, {@code SMALL_DELAY_MS}, {@code MEDIUM_DELAY_MS}, + * {@code LONG_DELAY_MS}. The idea here is that a SHORT is always + * discriminable from zero time, and always allows enough time for the + * small amounts of computation (creating a thread, calling a few + * methods, etc) needed to reach a timeout point. Similarly, a SMALL + * is always discriminable as larger than SHORT and smaller than + * MEDIUM. And so on. These constants are set to conservative values, + * but even so, if there is ever any doubt, they can all be increased + * in one spot to rerun tests on slower platforms. + * + *
  4. All threads generated must be joined inside each test case + * method (or {@code fail} to do so) before returning from the + * method. The {@code joinPool} method can be used to do this when + * using Executors. + * + *
+ * + *

Other notes + *

    + * + *
  • Usually, there is one testcase method per JSR166 method + * covering "normal" operation, and then as many exception-testing + * methods as there are exceptions the method can throw. Sometimes + * there are multiple tests per JSR166 method when the different + * "normal" behaviors differ significantly. And sometimes testcases + * cover multiple methods when they cannot be tested in isolation. + * + *
  • The documentation style for testcases is to provide as javadoc + * a simple sentence or two describing the property that the testcase + * method purports to test. The javadocs do not say anything about how + * the property is tested. To find out, read the code. + * + *
  • These tests are "conformance tests", and do not attempt to + * test throughput, latency, scalability or other performance factors + * (see the separate "jtreg" tests for a set intended to check these + * for the most central aspects of functionality.) So, most tests use + * the smallest sensible numbers of threads, collection sizes, etc + * needed to check basic conformance. + * + *
  • The test classes currently do not declare inclusion in + * any particular package to simplify things for people integrating + * them in TCK test suites. + * + *
  • As a convenience, the {@code main} of this class (JSR166TestCase) + * runs all JSR166 unit tests. + * + *
+ */ +public class JSR166TestCase extends TestCase { + private static final boolean useSecurityManager = + Boolean.getBoolean("jsr166.useSecurityManager"); + + protected static final boolean expensiveTests = + Boolean.getBoolean("jsr166.expensiveTests"); + + /** + * If true, also run tests that are not part of the official tck + * because they test unspecified implementation details. + */ + protected static final boolean testImplementationDetails = + Boolean.getBoolean("jsr166.testImplementationDetails"); + + /** + * If true, report on stdout all "slow" tests, that is, ones that + * take more than profileThreshold milliseconds to execute. + */ + private static final boolean profileTests = + Boolean.getBoolean("jsr166.profileTests"); + + /** + * The number of milliseconds that tests are permitted for + * execution without being reported, when profileTests is set. + */ + private static final long profileThreshold = + Long.getLong("jsr166.profileThreshold", 100); + + /** + * The number of repetitions per test (for tickling rare bugs). + */ + private static final int runsPerTest = + Integer.getInteger("jsr166.runsPerTest", 1); + + /** + * The number of repetitions of the test suite (for finding leaks?). + */ + private static final int suiteRuns = + Integer.getInteger("jsr166.suiteRuns", 1); + + /** + * The scaling factor to apply to standard delays used in tests. + */ + private static final int delayFactor = + Integer.getInteger("jsr166.delay.factor", 1); + + public JSR166TestCase() { super(); } + public JSR166TestCase(String name) { super(name); } + + /** + * A filter for tests to run, matching strings of the form + * methodName(className), e.g. "testInvokeAll5(ForkJoinPoolTest)" + * Usefully combined with jsr166.runsPerTest. + */ + private static final Pattern methodFilter = methodFilter(); + + private static Pattern methodFilter() { + String regex = System.getProperty("jsr166.methodFilter"); + return (regex == null) ? null : Pattern.compile(regex); + } + + // Instrumentation to debug very rare, but very annoying hung test runs. + static volatile TestCase currentTestCase; + // static volatile int currentRun = 0; + static { + Runnable checkForWedgedTest = new Runnable() { public void run() { + // Avoid spurious reports with enormous runsPerTest. + // A single test case run should never take more than 1 second. + // But let's cap it at the high end too ... + final int timeoutMinutes = + Math.min(15, Math.max(runsPerTest / 60, 1)); + for (TestCase lastTestCase = currentTestCase;;) { + try { MINUTES.sleep(timeoutMinutes); } + catch (InterruptedException unexpected) { break; } + if (lastTestCase == currentTestCase) { + System.err.printf( + "Looks like we're stuck running test: %s%n", + lastTestCase); +// System.err.printf( +// "Looks like we're stuck running test: %s (%d/%d)%n", +// lastTestCase, currentRun, runsPerTest); +// System.err.println("availableProcessors=" + +// Runtime.getRuntime().availableProcessors()); +// System.err.printf("cpu model = %s%n", cpuModel()); + dumpTestThreads(); + // one stack dump is probably enough; more would be spam + break; + } + lastTestCase = currentTestCase; + }}}; + Thread thread = new Thread(checkForWedgedTest, "checkForWedgedTest"); + thread.setDaemon(true); + thread.start(); + } + +// public static String cpuModel() { +// try { +// Matcher matcher = Pattern.compile("model name\\s*: (.*)") +// .matcher(new String( +// Files.readAllBytes(Paths.get("/proc/cpuinfo")), "UTF-8")); +// matcher.find(); +// return matcher.group(1); +// } catch (Exception ex) { return null; } +// } + + public void runBare() throws Throwable { + currentTestCase = this; + if (methodFilter == null + || methodFilter.matcher(toString()).find()) + super.runBare(); + } + + protected void runTest() throws Throwable { + for (int i = 0; i < runsPerTest; i++) { + // currentRun = i; + if (profileTests) + runTestProfiled(); + else + super.runTest(); + } + } + + protected void runTestProfiled() throws Throwable { + for (int i = 0; i < 2; i++) { + long startTime = System.nanoTime(); + super.runTest(); + long elapsedMillis = millisElapsedSince(startTime); + if (elapsedMillis < profileThreshold) + break; + // Never report first run of any test; treat it as a + // warmup run, notably to trigger all needed classloading, + if (i > 0) + System.out.printf("%n%s: %d%n", toString(), elapsedMillis); + } + } + + /** + * Runs all JSR166 unit tests using junit.textui.TestRunner. + */ + public static void main(String[] args) { + main(suite(), args); + } + + static class PithyResultPrinter extends junit.textui.ResultPrinter { + PithyResultPrinter(java.io.PrintStream writer) { super(writer); } + long runTime; + public void startTest(Test test) {} + protected void printHeader(long runTime) { + this.runTime = runTime; // defer printing for later + } + protected void printFooter(TestResult result) { + if (result.wasSuccessful()) { + getWriter().println("OK (" + result.runCount() + " tests)" + + " Time: " + elapsedTimeAsString(runTime)); + } else { + getWriter().println("Time: " + elapsedTimeAsString(runTime)); + super.printFooter(result); + } + } + } + + /** + * Returns a TestRunner that doesn't bother with unnecessary + * fluff, like printing a "." for each test case. + */ + static junit.textui.TestRunner newPithyTestRunner() { + junit.textui.TestRunner runner = new junit.textui.TestRunner(); + runner.setPrinter(new PithyResultPrinter(System.out)); + return runner; + } + + /** + * Runs all unit tests in the given test suite. + * Actual behavior influenced by jsr166.* system properties. + */ + static void main(Test suite, String[] args) { + if (useSecurityManager) { + System.err.println("Setting a permissive security manager"); + Policy.setPolicy(permissivePolicy()); + System.setSecurityManager(new SecurityManager()); + } + for (int i = 0; i < suiteRuns; i++) { + TestResult result = newPithyTestRunner().doRun(suite); + if (!result.wasSuccessful()) + System.exit(1); + System.gc(); + System.runFinalization(); + } + } + + public static TestSuite newTestSuite(Object... suiteOrClasses) { + TestSuite suite = new TestSuite(); + for (Object suiteOrClass : suiteOrClasses) { + if (suiteOrClass instanceof TestSuite) + suite.addTest((TestSuite) suiteOrClass); + else if (suiteOrClass instanceof Class) + suite.addTest(new TestSuite((Class) suiteOrClass)); + else + throw new ClassCastException("not a test suite or class"); + } + return suite; + } + + public static void addNamedTestClasses(TestSuite suite, + String... testClassNames) { + for (String testClassName : testClassNames) { + try { + Class testClass = Class.forName(testClassName); + Method m = testClass.getDeclaredMethod("suite", + new Class[0]); + suite.addTest(newTestSuite((Test)m.invoke(null))); + } catch (Exception e) { + throw new Error("Missing test class", e); + } + } + } + + public static final double JAVA_CLASS_VERSION; + public static final String JAVA_SPECIFICATION_VERSION; + static { + try { + JAVA_CLASS_VERSION = java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Double run() { + return Double.valueOf(System.getProperty("java.class.version"));}}); + JAVA_SPECIFICATION_VERSION = java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public String run() { + return System.getProperty("java.specification.version");}}); + } catch (Throwable t) { + throw new Error(t); + } + } + + public static boolean atLeastJava6() { return JAVA_CLASS_VERSION >= 50.0; } + public static boolean atLeastJava7() { return JAVA_CLASS_VERSION >= 51.0; } + public static boolean atLeastJava8() { return JAVA_CLASS_VERSION >= 52.0; } + public static boolean atLeastJava9() { + return JAVA_CLASS_VERSION >= 53.0 + // As of 2015-09, java9 still uses 52.0 class file version + || JAVA_SPECIFICATION_VERSION.matches("^(1\\.)?(9|[0-9][0-9])$"); + } + public static boolean atLeastJava10() { + return JAVA_CLASS_VERSION >= 54.0 + || JAVA_SPECIFICATION_VERSION.matches("^(1\\.)?[0-9][0-9]$"); + } + + /** + * Collects all JSR166 unit tests as one suite. + */ + public static Test suite() { + // Java7+ test classes + TestSuite suite = newTestSuite( + ForkJoinPoolTest.suite(), + ForkJoinTaskTest.suite(), + RecursiveActionTest.suite(), + RecursiveTaskTest.suite(), + LinkedTransferQueueTest.suite(), + PhaserTest.suite(), + ThreadLocalRandomTest.suite(), + AbstractExecutorServiceTest.suite(), + AbstractQueueTest.suite(), + AbstractQueuedSynchronizerTest.suite(), + AbstractQueuedLongSynchronizerTest.suite(), + ArrayBlockingQueueTest.suite(), + ArrayDequeTest.suite(), + AtomicBooleanTest.suite(), + AtomicIntegerArrayTest.suite(), + AtomicIntegerFieldUpdaterTest.suite(), + AtomicIntegerTest.suite(), + AtomicLongArrayTest.suite(), + AtomicLongFieldUpdaterTest.suite(), + AtomicLongTest.suite(), + AtomicMarkableReferenceTest.suite(), + AtomicReferenceArrayTest.suite(), + AtomicReferenceFieldUpdaterTest.suite(), + AtomicReferenceTest.suite(), + AtomicStampedReferenceTest.suite(), + ConcurrentHashMapTest.suite(), + ConcurrentLinkedDequeTest.suite(), + ConcurrentLinkedQueueTest.suite(), + ConcurrentSkipListMapTest.suite(), + ConcurrentSkipListSubMapTest.suite(), + ConcurrentSkipListSetTest.suite(), + ConcurrentSkipListSubSetTest.suite(), + CopyOnWriteArrayListTest.suite(), + CopyOnWriteArraySetTest.suite(), + CountDownLatchTest.suite(), + CyclicBarrierTest.suite(), + DelayQueueTest.suite(), + EntryTest.suite(), + ExchangerTest.suite(), + ExecutorsTest.suite(), + ExecutorCompletionServiceTest.suite(), + FutureTaskTest.suite(), + LinkedBlockingDequeTest.suite(), + LinkedBlockingQueueTest.suite(), + LinkedListTest.suite(), + LockSupportTest.suite(), + PriorityBlockingQueueTest.suite(), + PriorityQueueTest.suite(), + ReentrantLockTest.suite(), + ReentrantReadWriteLockTest.suite(), + ScheduledExecutorTest.suite(), + ScheduledExecutorSubclassTest.suite(), + SemaphoreTest.suite(), + SynchronousQueueTest.suite(), + SystemTest.suite(), + ThreadLocalTest.suite(), + ThreadPoolExecutorTest.suite(), + ThreadPoolExecutorSubclassTest.suite(), + ThreadTest.suite(), + TimeUnitTest.suite(), + TreeMapTest.suite(), + TreeSetTest.suite(), + TreeSubMapTest.suite(), + TreeSubSetTest.suite()); + + // Java8+ test classes + if (atLeastJava8()) { + String[] java8TestClassNames = { + "Atomic8Test", + "CompletableFutureTest", + "ConcurrentHashMap8Test", + "CountedCompleterTest", + "DoubleAccumulatorTest", + "DoubleAdderTest", + "ForkJoinPool8Test", + "ForkJoinTask8Test", + "LongAccumulatorTest", + "LongAdderTest", + "SplittableRandomTest", + "StampedLockTest", + "SubmissionPublisherTest", + "ThreadLocalRandom8Test", + }; + addNamedTestClasses(suite, java8TestClassNames); + } + + // Java9+ test classes + if (atLeastJava9()) { + String[] java9TestClassNames = { + // Currently empty, but expecting varhandle tests + }; + addNamedTestClasses(suite, java9TestClassNames); + } + + return suite; + } + + /** Returns list of junit-style test method names in given class. */ + public static ArrayList testMethodNames(Class testClass) { + Method[] methods = testClass.getDeclaredMethods(); + ArrayList names = new ArrayList(methods.length); + for (Method method : methods) { + if (method.getName().startsWith("test") + && Modifier.isPublic(method.getModifiers()) + // method.getParameterCount() requires jdk8+ + && method.getParameterTypes().length == 0) { + names.add(method.getName()); + } + } + return names; + } + + /** + * Returns junit-style testSuite for the given test class, but + * parameterized by passing extra data to each test. + */ + public static Test parameterizedTestSuite + (Class testClass, + Class dataClass, + ExtraData data) { + try { + TestSuite suite = new TestSuite(); + Constructor c = + testClass.getDeclaredConstructor(dataClass, String.class); + for (String methodName : testMethodNames(testClass)) + suite.addTest((Test) c.newInstance(data, methodName)); + return suite; + } catch (Exception e) { + throw new Error(e); + } + } + + /** + * Returns junit-style testSuite for the jdk8 extension of the + * given test class, but parameterized by passing extra data to + * each test. Uses reflection to allow compilation in jdk7. + */ + public static Test jdk8ParameterizedTestSuite + (Class testClass, + Class dataClass, + ExtraData data) { + if (atLeastJava8()) { + String name = testClass.getName(); + String name8 = name.replaceAll("Test$", "8Test"); + if (name.equals(name8)) throw new Error(name); + try { + return (Test) + Class.forName(name8) + .getMethod("testSuite", new Class[] { dataClass }) + .invoke(null, data); + } catch (Exception e) { + throw new Error(e); + } + } else { + return new TestSuite(); + } + } + + // Delays for timing-dependent tests, in milliseconds. + + public static long SHORT_DELAY_MS; + public static long SMALL_DELAY_MS; + public static long MEDIUM_DELAY_MS; + public static long LONG_DELAY_MS; + + /** + * Returns the shortest timed delay. This can be scaled up for + * slow machines using the jsr166.delay.factor system property. + */ + protected long getShortDelay() { + return 50 * delayFactor; + } + + /** + * Sets delays as multiples of SHORT_DELAY. + */ + protected void setDelays() { + SHORT_DELAY_MS = getShortDelay(); + SMALL_DELAY_MS = SHORT_DELAY_MS * 5; + MEDIUM_DELAY_MS = SHORT_DELAY_MS * 10; + LONG_DELAY_MS = SHORT_DELAY_MS * 200; + } + + /** + * Returns a timeout in milliseconds to be used in tests that + * verify that operations block or time out. + */ + long timeoutMillis() { + return SHORT_DELAY_MS / 4; + } + + /** + * Returns a new Date instance representing a time at least + * delayMillis milliseconds in the future. + */ + Date delayedDate(long delayMillis) { + // Add 1 because currentTimeMillis is known to round into the past. + return new Date(System.currentTimeMillis() + delayMillis + 1); + } + + /** + * The first exception encountered if any threadAssertXXX method fails. + */ + private final AtomicReference threadFailure + = new AtomicReference(null); + + /** + * Records an exception so that it can be rethrown later in the test + * harness thread, triggering a test case failure. Only the first + * failure is recorded; subsequent calls to this method from within + * the same test have no effect. + */ + public void threadRecordFailure(Throwable t) { + System.err.println(t); + dumpTestThreads(); + threadFailure.compareAndSet(null, t); + } + + public void setUp() { + setDelays(); + } + + void tearDownFail(String format, Object... args) { + String msg = toString() + ": " + String.format(format, args); + System.err.println(msg); + dumpTestThreads(); + throw new AssertionFailedError(msg); + } + + /** + * Extra checks that get done for all test cases. + * + * Triggers test case failure if any thread assertions have failed, + * by rethrowing, in the test harness thread, any exception recorded + * earlier by threadRecordFailure. + * + * Triggers test case failure if interrupt status is set in the main thread. + */ + public void tearDown() throws Exception { + Throwable t = threadFailure.getAndSet(null); + if (t != null) { + if (t instanceof Error) + throw (Error) t; + else if (t instanceof RuntimeException) + throw (RuntimeException) t; + else if (t instanceof Exception) + throw (Exception) t; + else { + AssertionFailedError afe = + new AssertionFailedError(t.toString()); + afe.initCause(t); + throw afe; + } + } + + if (Thread.interrupted()) + tearDownFail("interrupt status set in main thread"); + + checkForkJoinPoolThreadLeaks(); + } + + /** + * Finds missing PoolCleaners + */ + void checkForkJoinPoolThreadLeaks() throws InterruptedException { + Thread[] survivors = new Thread[7]; + int count = Thread.enumerate(survivors); + for (int i = 0; i < count; i++) { + Thread thread = survivors[i]; + String name = thread.getName(); + if (name.startsWith("ForkJoinPool-")) { + // give thread some time to terminate + thread.join(LONG_DELAY_MS); + if (thread.isAlive()) + tearDownFail("Found leaked ForkJoinPool thread thread=%s", + thread); + } + } + + if (!ForkJoinPool.commonPool() + .awaitQuiescence(LONG_DELAY_MS, MILLISECONDS)) + tearDownFail("ForkJoin common pool thread stuck"); + } + + /** + * Just like fail(reason), but additionally recording (using + * threadRecordFailure) any AssertionFailedError thrown, so that + * the current testcase will fail. + */ + public void threadFail(String reason) { + try { + fail(reason); + } catch (AssertionFailedError t) { + threadRecordFailure(t); + throw t; + } + } + + /** + * Just like assertTrue(b), but additionally recording (using + * threadRecordFailure) any AssertionFailedError thrown, so that + * the current testcase will fail. + */ + public void threadAssertTrue(boolean b) { + try { + assertTrue(b); + } catch (AssertionFailedError t) { + threadRecordFailure(t); + throw t; + } + } + + /** + * Just like assertFalse(b), but additionally recording (using + * threadRecordFailure) any AssertionFailedError thrown, so that + * the current testcase will fail. + */ + public void threadAssertFalse(boolean b) { + try { + assertFalse(b); + } catch (AssertionFailedError t) { + threadRecordFailure(t); + throw t; + } + } + + /** + * Just like assertNull(x), but additionally recording (using + * threadRecordFailure) any AssertionFailedError thrown, so that + * the current testcase will fail. + */ + public void threadAssertNull(Object x) { + try { + assertNull(x); + } catch (AssertionFailedError t) { + threadRecordFailure(t); + throw t; + } + } + + /** + * Just like assertEquals(x, y), but additionally recording (using + * threadRecordFailure) any AssertionFailedError thrown, so that + * the current testcase will fail. + */ + public void threadAssertEquals(long x, long y) { + try { + assertEquals(x, y); + } catch (AssertionFailedError t) { + threadRecordFailure(t); + throw t; + } + } + + /** + * Just like assertEquals(x, y), but additionally recording (using + * threadRecordFailure) any AssertionFailedError thrown, so that + * the current testcase will fail. + */ + public void threadAssertEquals(Object x, Object y) { + try { + assertEquals(x, y); + } catch (AssertionFailedError fail) { + threadRecordFailure(fail); + throw fail; + } catch (Throwable fail) { + threadUnexpectedException(fail); + } + } + + /** + * Just like assertSame(x, y), but additionally recording (using + * threadRecordFailure) any AssertionFailedError thrown, so that + * the current testcase will fail. + */ + public void threadAssertSame(Object x, Object y) { + try { + assertSame(x, y); + } catch (AssertionFailedError fail) { + threadRecordFailure(fail); + throw fail; + } + } + + /** + * Calls threadFail with message "should throw exception". + */ + public void threadShouldThrow() { + threadFail("should throw exception"); + } + + /** + * Calls threadFail with message "should throw" + exceptionName. + */ + public void threadShouldThrow(String exceptionName) { + threadFail("should throw " + exceptionName); + } + + /** + * Records the given exception using {@link #threadRecordFailure}, + * then rethrows the exception, wrapping it in an + * AssertionFailedError if necessary. + */ + public void threadUnexpectedException(Throwable t) { + threadRecordFailure(t); + t.printStackTrace(); + if (t instanceof RuntimeException) + throw (RuntimeException) t; + else if (t instanceof Error) + throw (Error) t; + else { + AssertionFailedError afe = + new AssertionFailedError("unexpected exception: " + t); + afe.initCause(t); + throw afe; + } + } + + /** + * Delays, via Thread.sleep, for the given millisecond delay, but + * if the sleep is shorter than specified, may re-sleep or yield + * until time elapses. Ensures that the given time, as measured + * by System.nanoTime(), has elapsed. + */ + static void delay(long millis) throws InterruptedException { + long nanos = millis * (1000 * 1000); + final long wakeupTime = System.nanoTime() + nanos; + do { + if (millis > 0L) + Thread.sleep(millis); + else // too short to sleep + Thread.yield(); + nanos = wakeupTime - System.nanoTime(); + millis = nanos / (1000 * 1000); + } while (nanos >= 0L); + } + + /** + * Allows use of try-with-resources with per-test thread pools. + */ + class PoolCleaner implements AutoCloseable { + private final ExecutorService pool; + public PoolCleaner(ExecutorService pool) { this.pool = pool; } + public void close() { joinPool(pool); } + } + + /** + * An extension of PoolCleaner that has an action to release the pool. + */ + class PoolCleanerWithReleaser extends PoolCleaner { + private final Runnable releaser; + public PoolCleanerWithReleaser(ExecutorService pool, Runnable releaser) { + super(pool); + this.releaser = releaser; + } + public void close() { + try { + releaser.run(); + } finally { + super.close(); + } + } + } + + PoolCleaner cleaner(ExecutorService pool) { + return new PoolCleaner(pool); + } + + PoolCleaner cleaner(ExecutorService pool, Runnable releaser) { + return new PoolCleanerWithReleaser(pool, releaser); + } + + PoolCleaner cleaner(ExecutorService pool, CountDownLatch latch) { + return new PoolCleanerWithReleaser(pool, releaser(latch)); + } + + Runnable releaser(final CountDownLatch latch) { + return new Runnable() { public void run() { + do { latch.countDown(); } + while (latch.getCount() > 0); + }}; + } + + /** + * Waits out termination of a thread pool or fails doing so. + */ + void joinPool(ExecutorService pool) { + try { + pool.shutdown(); + if (!pool.awaitTermination(2 * LONG_DELAY_MS, MILLISECONDS)) { + try { + threadFail("ExecutorService " + pool + + " did not terminate in a timely manner"); + } finally { + // last resort, for the benefit of subsequent tests + pool.shutdownNow(); + pool.awaitTermination(MEDIUM_DELAY_MS, MILLISECONDS); + } + } + } catch (SecurityException ok) { + // Allowed in case test doesn't have privs + } catch (InterruptedException fail) { + threadFail("Unexpected InterruptedException"); + } + } + + /** Like Runnable, but with the freedom to throw anything */ + interface Action { public void run() throws Throwable; } + + /** + * Runs all the given actions in parallel, failing if any fail. + * Useful for running multiple variants of tests that are + * necessarily individually slow because they must block. + */ + void testInParallel(Action ... actions) { + ExecutorService pool = Executors.newCachedThreadPool(); + try (PoolCleaner cleaner = cleaner(pool)) { + ArrayList> futures = new ArrayList<>(actions.length); + for (final Action action : actions) + futures.add(pool.submit(new CheckedRunnable() { + public void realRun() throws Throwable { action.run();}})); + for (Future future : futures) + try { + assertNull(future.get(LONG_DELAY_MS, MILLISECONDS)); + } catch (ExecutionException ex) { + threadUnexpectedException(ex.getCause()); + } catch (Exception ex) { + threadUnexpectedException(ex); + } + } + } + + /** + * A debugging tool to print stack traces of most threads, as jstack does. + * Uninteresting threads are filtered out. + */ + static void dumpTestThreads() { + ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); + System.err.println("------ stacktrace dump start ------"); + for (ThreadInfo info : threadMXBean.dumpAllThreads(true, true)) { + String name = info.getThreadName(); + if ("Signal Dispatcher".equals(name)) + continue; + if ("Reference Handler".equals(name) + && info.getLockName().startsWith("java.lang.ref.Reference$Lock")) + continue; + if ("Finalizer".equals(name) + && info.getLockName().startsWith("java.lang.ref.ReferenceQueue$Lock")) + continue; + if ("checkForWedgedTest".equals(name)) + continue; + System.err.print(info); + } + System.err.println("------ stacktrace dump end ------"); + } + + /** + * Checks that thread does not terminate within the default + * millisecond delay of {@code timeoutMillis()}. + */ + void assertThreadStaysAlive(Thread thread) { + assertThreadStaysAlive(thread, timeoutMillis()); + } + + /** + * Checks that thread does not terminate within the given millisecond delay. + */ + void assertThreadStaysAlive(Thread thread, long millis) { + try { + // No need to optimize the failing case via Thread.join. + delay(millis); + assertTrue(thread.isAlive()); + } catch (InterruptedException fail) { + threadFail("Unexpected InterruptedException"); + } + } + + /** + * Checks that the threads do not terminate within the default + * millisecond delay of {@code timeoutMillis()}. + */ + void assertThreadsStayAlive(Thread... threads) { + assertThreadsStayAlive(timeoutMillis(), threads); + } + + /** + * Checks that the threads do not terminate within the given millisecond delay. + */ + void assertThreadsStayAlive(long millis, Thread... threads) { + try { + // No need to optimize the failing case via Thread.join. + delay(millis); + for (Thread thread : threads) + assertTrue(thread.isAlive()); + } catch (InterruptedException fail) { + threadFail("Unexpected InterruptedException"); + } + } + + /** + * Checks that future.get times out, with the default timeout of + * {@code timeoutMillis()}. + */ + void assertFutureTimesOut(Future future) { + assertFutureTimesOut(future, timeoutMillis()); + } + + /** + * Checks that future.get times out, with the given millisecond timeout. + */ + void assertFutureTimesOut(Future future, long timeoutMillis) { + long startTime = System.nanoTime(); + try { + future.get(timeoutMillis, MILLISECONDS); + shouldThrow(); + } catch (TimeoutException success) { + } catch (Exception fail) { + threadUnexpectedException(fail); + } finally { future.cancel(true); } + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + } + + /** + * Fails with message "should throw exception". + */ + public void shouldThrow() { + fail("Should throw exception"); + } + + /** + * Fails with message "should throw " + exceptionName. + */ + public void shouldThrow(String exceptionName) { + fail("Should throw " + exceptionName); + } + + /** + * The number of elements to place in collections, arrays, etc. + */ + public static final int SIZE = 20; + + // Some convenient Integer constants + + public static final Integer zero = new Integer(0); + public static final Integer one = new Integer(1); + public static final Integer two = new Integer(2); + public static final Integer three = new Integer(3); + public static final Integer four = new Integer(4); + public static final Integer five = new Integer(5); + public static final Integer six = new Integer(6); + public static final Integer seven = new Integer(7); + public static final Integer eight = new Integer(8); + public static final Integer nine = new Integer(9); + public static final Integer m1 = new Integer(-1); + public static final Integer m2 = new Integer(-2); + public static final Integer m3 = new Integer(-3); + public static final Integer m4 = new Integer(-4); + public static final Integer m5 = new Integer(-5); + public static final Integer m6 = new Integer(-6); + public static final Integer m10 = new Integer(-10); + + /** + * Runs Runnable r with a security policy that permits precisely + * the specified permissions. If there is no current security + * manager, the runnable is run twice, both with and without a + * security manager. We require that any security manager permit + * getPolicy/setPolicy. + */ + public void runWithPermissions(Runnable r, Permission... permissions) { + SecurityManager sm = System.getSecurityManager(); + if (sm == null) { + r.run(); + } + runWithSecurityManagerWithPermissions(r, permissions); + } + + /** + * Runs Runnable r with a security policy that permits precisely + * the specified permissions. If there is no current security + * manager, a temporary one is set for the duration of the + * Runnable. We require that any security manager permit + * getPolicy/setPolicy. + */ + public void runWithSecurityManagerWithPermissions(Runnable r, + Permission... permissions) { + SecurityManager sm = System.getSecurityManager(); + if (sm == null) { + Policy savedPolicy = Policy.getPolicy(); + try { + Policy.setPolicy(permissivePolicy()); + System.setSecurityManager(new SecurityManager()); + runWithSecurityManagerWithPermissions(r, permissions); + } finally { + System.setSecurityManager(null); + Policy.setPolicy(savedPolicy); + } + } else { + Policy savedPolicy = Policy.getPolicy(); + AdjustablePolicy policy = new AdjustablePolicy(permissions); + Policy.setPolicy(policy); + + try { + r.run(); + } finally { + policy.addPermission(new SecurityPermission("setPolicy")); + Policy.setPolicy(savedPolicy); + } + } + } + + /** + * Runs a runnable without any permissions. + */ + public void runWithoutPermissions(Runnable r) { + runWithPermissions(r); + } + + /** + * A security policy where new permissions can be dynamically added + * or all cleared. + */ + public static class AdjustablePolicy extends java.security.Policy { + Permissions perms = new Permissions(); + AdjustablePolicy(Permission... permissions) { + for (Permission permission : permissions) + perms.add(permission); + } + void addPermission(Permission perm) { perms.add(perm); } + void clearPermissions() { perms = new Permissions(); } + public PermissionCollection getPermissions(CodeSource cs) { + return perms; + } + public PermissionCollection getPermissions(ProtectionDomain pd) { + return perms; + } + public boolean implies(ProtectionDomain pd, Permission p) { + return perms.implies(p); + } + public void refresh() {} + public String toString() { + List ps = new ArrayList(); + for (Enumeration e = perms.elements(); e.hasMoreElements();) + ps.add(e.nextElement()); + return "AdjustablePolicy with permissions " + ps; + } + } + + /** + * Returns a policy containing all the permissions we ever need. + */ + public static Policy permissivePolicy() { + return new AdjustablePolicy + // Permissions j.u.c. needs directly + (new RuntimePermission("modifyThread"), + new RuntimePermission("getClassLoader"), + new RuntimePermission("setContextClassLoader"), + // Permissions needed to change permissions! + new SecurityPermission("getPolicy"), + new SecurityPermission("setPolicy"), + new RuntimePermission("setSecurityManager"), + // Permissions needed by the junit test harness + new RuntimePermission("accessDeclaredMembers"), + new PropertyPermission("*", "read"), + new java.io.FilePermission("<>", "read")); + } + + /** + * Sleeps until the given time has elapsed. + * Throws AssertionFailedError if interrupted. + */ + void sleep(long millis) { + try { + delay(millis); + } catch (InterruptedException fail) { + AssertionFailedError afe = + new AssertionFailedError("Unexpected InterruptedException"); + afe.initCause(fail); + throw afe; + } + } + + /** + * Spin-waits up to the specified number of milliseconds for the given + * thread to enter a wait state: BLOCKED, WAITING, or TIMED_WAITING. + */ + void waitForThreadToEnterWaitState(Thread thread, long timeoutMillis) { + long startTime = System.nanoTime(); + for (;;) { + Thread.State s = thread.getState(); + if (s == Thread.State.BLOCKED || + s == Thread.State.WAITING || + s == Thread.State.TIMED_WAITING) + return; + else if (s == Thread.State.TERMINATED) + fail("Unexpected thread termination"); + else if (millisElapsedSince(startTime) > timeoutMillis) { + threadAssertTrue(thread.isAlive()); + return; + } + Thread.yield(); + } + } + + /** + * Waits up to LONG_DELAY_MS for the given thread to enter a wait + * state: BLOCKED, WAITING, or TIMED_WAITING. + */ + void waitForThreadToEnterWaitState(Thread thread) { + waitForThreadToEnterWaitState(thread, LONG_DELAY_MS); + } + + /** + * Returns the number of milliseconds since time given by + * startNanoTime, which must have been previously returned from a + * call to {@link System#nanoTime()}. + */ + static long millisElapsedSince(long startNanoTime) { + return NANOSECONDS.toMillis(System.nanoTime() - startNanoTime); + } + +// void assertTerminatesPromptly(long timeoutMillis, Runnable r) { +// long startTime = System.nanoTime(); +// try { +// r.run(); +// } catch (Throwable fail) { threadUnexpectedException(fail); } +// if (millisElapsedSince(startTime) > timeoutMillis/2) +// throw new AssertionFailedError("did not return promptly"); +// } + +// void assertTerminatesPromptly(Runnable r) { +// assertTerminatesPromptly(LONG_DELAY_MS/2, r); +// } + + /** + * Checks that timed f.get() returns the expected value, and does not + * wait for the timeout to elapse before returning. + */ + void checkTimedGet(Future f, T expectedValue, long timeoutMillis) { + long startTime = System.nanoTime(); + try { + assertEquals(expectedValue, f.get(timeoutMillis, MILLISECONDS)); + } catch (Throwable fail) { threadUnexpectedException(fail); } + if (millisElapsedSince(startTime) > timeoutMillis/2) + throw new AssertionFailedError("timed get did not return promptly"); + } + + void checkTimedGet(Future f, T expectedValue) { + checkTimedGet(f, expectedValue, LONG_DELAY_MS); + } + + /** + * Returns a new started daemon Thread running the given runnable. + */ + Thread newStartedThread(Runnable runnable) { + Thread t = new Thread(runnable); + t.setDaemon(true); + t.start(); + return t; + } + + /** + * Waits for the specified time (in milliseconds) for the thread + * to terminate (using {@link Thread#join(long)}), else interrupts + * the thread (in the hope that it may terminate later) and fails. + */ + void awaitTermination(Thread t, long timeoutMillis) { + try { + t.join(timeoutMillis); + } catch (InterruptedException fail) { + threadUnexpectedException(fail); + } finally { + if (t.getState() != Thread.State.TERMINATED) { + t.interrupt(); + threadFail("timed out waiting for thread to terminate"); + } + } + } + + /** + * Waits for LONG_DELAY_MS milliseconds for the thread to + * terminate (using {@link Thread#join(long)}), else interrupts + * the thread (in the hope that it may terminate later) and fails. + */ + void awaitTermination(Thread t) { + awaitTermination(t, LONG_DELAY_MS); + } + + // Some convenient Runnable classes + + public abstract class CheckedRunnable implements Runnable { + protected abstract void realRun() throws Throwable; + + public final void run() { + try { + realRun(); + } catch (Throwable fail) { + threadUnexpectedException(fail); + } + } + } + + public abstract class RunnableShouldThrow implements Runnable { + protected abstract void realRun() throws Throwable; + + final Class exceptionClass; + + RunnableShouldThrow(Class exceptionClass) { + this.exceptionClass = exceptionClass; + } + + public final void run() { + try { + realRun(); + threadShouldThrow(exceptionClass.getSimpleName()); + } catch (Throwable t) { + if (! exceptionClass.isInstance(t)) + threadUnexpectedException(t); + } + } + } + + public abstract class ThreadShouldThrow extends Thread { + protected abstract void realRun() throws Throwable; + + final Class exceptionClass; + + ThreadShouldThrow(Class exceptionClass) { + this.exceptionClass = exceptionClass; + } + + public final void run() { + try { + realRun(); + threadShouldThrow(exceptionClass.getSimpleName()); + } catch (Throwable t) { + if (! exceptionClass.isInstance(t)) + threadUnexpectedException(t); + } + } + } + + public abstract class CheckedInterruptedRunnable implements Runnable { + protected abstract void realRun() throws Throwable; + + public final void run() { + try { + realRun(); + threadShouldThrow("InterruptedException"); + } catch (InterruptedException success) { + threadAssertFalse(Thread.interrupted()); + } catch (Throwable fail) { + threadUnexpectedException(fail); + } + } + } + + public abstract class CheckedCallable implements Callable { + protected abstract T realCall() throws Throwable; + + public final T call() { + try { + return realCall(); + } catch (Throwable fail) { + threadUnexpectedException(fail); + return null; + } + } + } + + public abstract class CheckedInterruptedCallable + implements Callable { + protected abstract T realCall() throws Throwable; + + public final T call() { + try { + T result = realCall(); + threadShouldThrow("InterruptedException"); + return result; + } catch (InterruptedException success) { + threadAssertFalse(Thread.interrupted()); + } catch (Throwable fail) { + threadUnexpectedException(fail); + } + return null; + } + } + + public static class NoOpRunnable implements Runnable { + public void run() {} + } + + public static class NoOpCallable implements Callable { + public Object call() { return Boolean.TRUE; } + } + + public static final String TEST_STRING = "a test string"; + + public static class StringTask implements Callable { + final String value; + public StringTask() { this(TEST_STRING); } + public StringTask(String value) { this.value = value; } + public String call() { return value; } + } + + public Callable latchAwaitingStringTask(final CountDownLatch latch) { + return new CheckedCallable() { + protected String realCall() { + try { + latch.await(); + } catch (InterruptedException quittingTime) {} + return TEST_STRING; + }}; + } + + public Runnable countDowner(final CountDownLatch latch) { + return new CheckedRunnable() { + public void realRun() throws InterruptedException { + latch.countDown(); + }}; + } + + class LatchAwaiter extends CheckedRunnable { + static final int NEW = 0; + static final int RUNNING = 1; + static final int DONE = 2; + final CountDownLatch latch; + int state = NEW; + LatchAwaiter(CountDownLatch latch) { this.latch = latch; } + public void realRun() throws InterruptedException { + state = 1; + await(latch); + state = 2; + } + } + + public LatchAwaiter awaiter(CountDownLatch latch) { + return new LatchAwaiter(latch); + } + + public void await(CountDownLatch latch) { + try { + if (!latch.await(LONG_DELAY_MS, MILLISECONDS)) + fail("timed out waiting for CountDownLatch for " + + (LONG_DELAY_MS/1000) + " sec"); + } catch (Throwable fail) { + threadUnexpectedException(fail); + } + } + + public void await(Semaphore semaphore) { + try { + if (!semaphore.tryAcquire(LONG_DELAY_MS, MILLISECONDS)) + fail("timed out waiting for Semaphore for " + + (LONG_DELAY_MS/1000) + " sec"); + } catch (Throwable fail) { + threadUnexpectedException(fail); + } + } + +// /** +// * Spin-waits up to LONG_DELAY_MS until flag becomes true. +// */ +// public void await(AtomicBoolean flag) { +// await(flag, LONG_DELAY_MS); +// } + +// /** +// * Spin-waits up to the specified timeout until flag becomes true. +// */ +// public void await(AtomicBoolean flag, long timeoutMillis) { +// long startTime = System.nanoTime(); +// while (!flag.get()) { +// if (millisElapsedSince(startTime) > timeoutMillis) +// throw new AssertionFailedError("timed out"); +// Thread.yield(); +// } +// } + + public static class NPETask implements Callable { + public String call() { throw new NullPointerException(); } + } + + public static class CallableOne implements Callable { + public Integer call() { return one; } + } + + public class ShortRunnable extends CheckedRunnable { + protected void realRun() throws Throwable { + delay(SHORT_DELAY_MS); + } + } + + public class ShortInterruptedRunnable extends CheckedInterruptedRunnable { + protected void realRun() throws InterruptedException { + delay(SHORT_DELAY_MS); + } + } + + public class SmallRunnable extends CheckedRunnable { + protected void realRun() throws Throwable { + delay(SMALL_DELAY_MS); + } + } + + public class SmallPossiblyInterruptedRunnable extends CheckedRunnable { + protected void realRun() { + try { + delay(SMALL_DELAY_MS); + } catch (InterruptedException ok) {} + } + } + + public class SmallCallable extends CheckedCallable { + protected Object realCall() throws InterruptedException { + delay(SMALL_DELAY_MS); + return Boolean.TRUE; + } + } + + public class MediumRunnable extends CheckedRunnable { + protected void realRun() throws Throwable { + delay(MEDIUM_DELAY_MS); + } + } + + public class MediumInterruptedRunnable extends CheckedInterruptedRunnable { + protected void realRun() throws InterruptedException { + delay(MEDIUM_DELAY_MS); + } + } + + public Runnable possiblyInterruptedRunnable(final long timeoutMillis) { + return new CheckedRunnable() { + protected void realRun() { + try { + delay(timeoutMillis); + } catch (InterruptedException ok) {} + }}; + } + + public class MediumPossiblyInterruptedRunnable extends CheckedRunnable { + protected void realRun() { + try { + delay(MEDIUM_DELAY_MS); + } catch (InterruptedException ok) {} + } + } + + public class LongPossiblyInterruptedRunnable extends CheckedRunnable { + protected void realRun() { + try { + delay(LONG_DELAY_MS); + } catch (InterruptedException ok) {} + } + } + + /** + * For use as ThreadFactory in constructors + */ + public static class SimpleThreadFactory implements ThreadFactory { + public Thread newThread(Runnable r) { + return new Thread(r); + } + } + + public interface TrackedRunnable extends Runnable { + boolean isDone(); + } + + public static TrackedRunnable trackedRunnable(final long timeoutMillis) { + return new TrackedRunnable() { + private volatile boolean done = false; + public boolean isDone() { return done; } + public void run() { + try { + delay(timeoutMillis); + done = true; + } catch (InterruptedException ok) {} + } + }; + } + + public static class TrackedShortRunnable implements Runnable { + public volatile boolean done = false; + public void run() { + try { + delay(SHORT_DELAY_MS); + done = true; + } catch (InterruptedException ok) {} + } + } + + public static class TrackedSmallRunnable implements Runnable { + public volatile boolean done = false; + public void run() { + try { + delay(SMALL_DELAY_MS); + done = true; + } catch (InterruptedException ok) {} + } + } + + public static class TrackedMediumRunnable implements Runnable { + public volatile boolean done = false; + public void run() { + try { + delay(MEDIUM_DELAY_MS); + done = true; + } catch (InterruptedException ok) {} + } + } + + public static class TrackedLongRunnable implements Runnable { + public volatile boolean done = false; + public void run() { + try { + delay(LONG_DELAY_MS); + done = true; + } catch (InterruptedException ok) {} + } + } + + public static class TrackedNoOpRunnable implements Runnable { + public volatile boolean done = false; + public void run() { + done = true; + } + } + + public static class TrackedCallable implements Callable { + public volatile boolean done = false; + public Object call() { + try { + delay(SMALL_DELAY_MS); + done = true; + } catch (InterruptedException ok) {} + return Boolean.TRUE; + } + } + + /** + * Analog of CheckedRunnable for RecursiveAction + */ + public abstract class CheckedRecursiveAction extends RecursiveAction { + protected abstract void realCompute() throws Throwable; + + @Override protected final void compute() { + try { + realCompute(); + } catch (Throwable fail) { + threadUnexpectedException(fail); + } + } + } + + /** + * Analog of CheckedCallable for RecursiveTask + */ + public abstract class CheckedRecursiveTask extends RecursiveTask { + protected abstract T realCompute() throws Throwable; + + @Override protected final T compute() { + try { + return realCompute(); + } catch (Throwable fail) { + threadUnexpectedException(fail); + return null; + } + } + } + + /** + * For use as RejectedExecutionHandler in constructors + */ + public static class NoOpREHandler implements RejectedExecutionHandler { + public void rejectedExecution(Runnable r, + ThreadPoolExecutor executor) {} + } + + /** + * A CyclicBarrier that uses timed await and fails with + * AssertionFailedErrors instead of throwing checked exceptions. + */ + public class CheckedBarrier extends CyclicBarrier { + public CheckedBarrier(int parties) { super(parties); } + + public int await() { + try { + return super.await(2 * LONG_DELAY_MS, MILLISECONDS); + } catch (TimeoutException timedOut) { + throw new AssertionFailedError("timed out"); + } catch (Exception fail) { + AssertionFailedError afe = + new AssertionFailedError("Unexpected exception: " + fail); + afe.initCause(fail); + throw afe; + } + } + } + + void checkEmpty(BlockingQueue q) { + try { + assertTrue(q.isEmpty()); + assertEquals(0, q.size()); + assertNull(q.peek()); + assertNull(q.poll()); + assertNull(q.poll(0, MILLISECONDS)); + assertEquals(q.toString(), "[]"); + assertTrue(Arrays.equals(q.toArray(), new Object[0])); + assertFalse(q.iterator().hasNext()); + try { + q.element(); + shouldThrow(); + } catch (NoSuchElementException success) {} + try { + q.iterator().next(); + shouldThrow(); + } catch (NoSuchElementException success) {} + try { + q.remove(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } catch (InterruptedException fail) { threadUnexpectedException(fail); } + } + + void assertSerialEquals(Object x, Object y) { + assertTrue(Arrays.equals(serialBytes(x), serialBytes(y))); + } + + void assertNotSerialEquals(Object x, Object y) { + assertFalse(Arrays.equals(serialBytes(x), serialBytes(y))); + } + + byte[] serialBytes(Object o) { + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(o); + oos.flush(); + oos.close(); + return bos.toByteArray(); + } catch (Throwable fail) { + threadUnexpectedException(fail); + return new byte[0]; + } + } + + @SuppressWarnings("unchecked") + T serialClone(T o) { + try { + ObjectInputStream ois = new ObjectInputStream + (new ByteArrayInputStream(serialBytes(o))); + T clone = (T) ois.readObject(); + assertSame(o.getClass(), clone.getClass()); + return clone; + } catch (Throwable fail) { + threadUnexpectedException(fail); + return null; + } + } + + public void assertThrows(Class expectedExceptionClass, + Runnable... throwingActions) { + for (Runnable throwingAction : throwingActions) { + boolean threw = false; + try { throwingAction.run(); } + catch (Throwable t) { + threw = true; + if (!expectedExceptionClass.isInstance(t)) { + AssertionFailedError afe = + new AssertionFailedError + ("Expected " + expectedExceptionClass.getName() + + ", got " + t.getClass().getName()); + afe.initCause(t); + threadUnexpectedException(afe); + } + } + if (!threw) + shouldThrow(expectedExceptionClass.getName()); + } + } + + public void assertIteratorExhausted(Iterator it) { + try { + it.next(); + shouldThrow(); + } catch (NoSuchElementException success) {} + assertFalse(it.hasNext()); + } +} diff --git a/jdk/test/java/util/concurrent/tck/LinkedBlockingDequeTest.java b/jdk/test/java/util/concurrent/tck/LinkedBlockingDequeTest.java new file mode 100644 index 00000000000..138799035cb --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/LinkedBlockingDequeTest.java @@ -0,0 +1,1848 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Deque; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.concurrent.BlockingDeque; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingDeque; + +import junit.framework.Test; + +public class LinkedBlockingDequeTest extends JSR166TestCase { + + public static class Unbounded extends BlockingQueueTest { + protected BlockingQueue emptyCollection() { + return new LinkedBlockingDeque(); + } + } + + public static class Bounded extends BlockingQueueTest { + protected BlockingQueue emptyCollection() { + return new LinkedBlockingDeque(SIZE); + } + } + + public static void main(String[] args) { + main(suite(), args); + } + + public static Test suite() { + return newTestSuite(LinkedBlockingDequeTest.class, + new Unbounded().testSuite(), + new Bounded().testSuite()); + } + + /** + * Returns a new deque of given size containing consecutive + * Integers 0 ... n. + */ + private LinkedBlockingDeque populatedDeque(int n) { + LinkedBlockingDeque q = + new LinkedBlockingDeque(n); + assertTrue(q.isEmpty()); + for (int i = 0; i < n; i++) + assertTrue(q.offer(new Integer(i))); + assertFalse(q.isEmpty()); + assertEquals(0, q.remainingCapacity()); + assertEquals(n, q.size()); + return q; + } + + /** + * isEmpty is true before add, false after + */ + public void testEmpty() { + LinkedBlockingDeque q = new LinkedBlockingDeque(); + assertTrue(q.isEmpty()); + q.add(new Integer(1)); + assertFalse(q.isEmpty()); + q.add(new Integer(2)); + q.removeFirst(); + q.removeFirst(); + assertTrue(q.isEmpty()); + } + + /** + * size changes when elements added and removed + */ + public void testSize() { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(SIZE - i, q.size()); + q.removeFirst(); + } + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.size()); + q.add(new Integer(i)); + } + } + + /** + * offerFirst(null) throws NullPointerException + */ + public void testOfferFirstNull() { + LinkedBlockingDeque q = new LinkedBlockingDeque(); + try { + q.offerFirst(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * offerLast(null) throws NullPointerException + */ + public void testOfferLastNull() { + LinkedBlockingDeque q = new LinkedBlockingDeque(); + try { + q.offerLast(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * OfferFirst succeeds + */ + public void testOfferFirst() { + LinkedBlockingDeque q = new LinkedBlockingDeque(); + assertTrue(q.offerFirst(new Integer(0))); + assertTrue(q.offerFirst(new Integer(1))); + } + + /** + * OfferLast succeeds + */ + public void testOfferLast() { + LinkedBlockingDeque q = new LinkedBlockingDeque(); + assertTrue(q.offerLast(new Integer(0))); + assertTrue(q.offerLast(new Integer(1))); + } + + /** + * pollFirst succeeds unless empty + */ + public void testPollFirst() { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.pollFirst()); + } + assertNull(q.pollFirst()); + } + + /** + * pollLast succeeds unless empty + */ + public void testPollLast() { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = SIZE - 1; i >= 0; --i) { + assertEquals(i, q.pollLast()); + } + assertNull(q.pollLast()); + } + + /** + * peekFirst returns next element, or null if empty + */ + public void testPeekFirst() { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.peekFirst()); + assertEquals(i, q.pollFirst()); + assertTrue(q.peekFirst() == null || + !q.peekFirst().equals(i)); + } + assertNull(q.peekFirst()); + } + + /** + * peek returns next element, or null if empty + */ + public void testPeek() { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.peek()); + assertEquals(i, q.pollFirst()); + assertTrue(q.peek() == null || + !q.peek().equals(i)); + } + assertNull(q.peek()); + } + + /** + * peekLast returns next element, or null if empty + */ + public void testPeekLast() { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = SIZE - 1; i >= 0; --i) { + assertEquals(i, q.peekLast()); + assertEquals(i, q.pollLast()); + assertTrue(q.peekLast() == null || + !q.peekLast().equals(i)); + } + assertNull(q.peekLast()); + } + + /** + * getFirst() returns first element, or throws NSEE if empty + */ + public void testFirstElement() { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.getFirst()); + assertEquals(i, q.pollFirst()); + } + try { + q.getFirst(); + shouldThrow(); + } catch (NoSuchElementException success) {} + assertNull(q.peekFirst()); + } + + /** + * getLast() returns last element, or throws NSEE if empty + */ + public void testLastElement() { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = SIZE - 1; i >= 0; --i) { + assertEquals(i, q.getLast()); + assertEquals(i, q.pollLast()); + } + try { + q.getLast(); + shouldThrow(); + } catch (NoSuchElementException success) {} + assertNull(q.peekLast()); + } + + /** + * removeFirst() removes first element, or throws NSEE if empty + */ + public void testRemoveFirst() { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.removeFirst()); + } + try { + q.removeFirst(); + shouldThrow(); + } catch (NoSuchElementException success) {} + assertNull(q.peekFirst()); + } + + /** + * removeLast() removes last element, or throws NSEE if empty + */ + public void testRemoveLast() { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = SIZE - 1; i >= 0; --i) { + assertEquals(i, q.removeLast()); + } + try { + q.removeLast(); + shouldThrow(); + } catch (NoSuchElementException success) {} + assertNull(q.peekLast()); + } + + /** + * remove removes next element, or throws NSEE if empty + */ + public void testRemove() { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.remove()); + } + try { + q.remove(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * removeFirstOccurrence(x) removes x and returns true if present + */ + public void testRemoveFirstOccurrence() { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = 1; i < SIZE; i += 2) { + assertTrue(q.removeFirstOccurrence(new Integer(i))); + } + for (int i = 0; i < SIZE; i += 2) { + assertTrue(q.removeFirstOccurrence(new Integer(i))); + assertFalse(q.removeFirstOccurrence(new Integer(i + 1))); + } + assertTrue(q.isEmpty()); + } + + /** + * removeLastOccurrence(x) removes x and returns true if present + */ + public void testRemoveLastOccurrence() { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = 1; i < SIZE; i += 2) { + assertTrue(q.removeLastOccurrence(new Integer(i))); + } + for (int i = 0; i < SIZE; i += 2) { + assertTrue(q.removeLastOccurrence(new Integer(i))); + assertFalse(q.removeLastOccurrence(new Integer(i + 1))); + } + assertTrue(q.isEmpty()); + } + + /** + * peekFirst returns element inserted with addFirst + */ + public void testAddFirst() { + LinkedBlockingDeque q = populatedDeque(3); + q.pollLast(); + q.addFirst(four); + assertSame(four, q.peekFirst()); + } + + /** + * peekLast returns element inserted with addLast + */ + public void testAddLast() { + LinkedBlockingDeque q = populatedDeque(3); + q.pollLast(); + q.addLast(four); + assertSame(four, q.peekLast()); + } + + /** + * A new deque has the indicated capacity, or Integer.MAX_VALUE if + * none given + */ + public void testConstructor1() { + assertEquals(SIZE, new LinkedBlockingDeque(SIZE).remainingCapacity()); + assertEquals(Integer.MAX_VALUE, new LinkedBlockingDeque().remainingCapacity()); + } + + /** + * Constructor throws IllegalArgumentException if capacity argument nonpositive + */ + public void testConstructor2() { + try { + new LinkedBlockingDeque(0); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Initializing from null Collection throws NullPointerException + */ + public void testConstructor3() { + try { + new LinkedBlockingDeque(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing from Collection of null elements throws NullPointerException + */ + public void testConstructor4() { + Collection elements = Arrays.asList(new Integer[SIZE]); + try { + new LinkedBlockingDeque(elements); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing from Collection with some null elements throws + * NullPointerException + */ + public void testConstructor5() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = i; + Collection elements = Arrays.asList(ints); + try { + new LinkedBlockingDeque(elements); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Deque contains all elements of collection used to initialize + */ + public void testConstructor6() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = i; + LinkedBlockingDeque q = new LinkedBlockingDeque(Arrays.asList(ints)); + for (int i = 0; i < SIZE; ++i) + assertEquals(ints[i], q.poll()); + } + + /** + * Deque transitions from empty to full when elements added + */ + public void testEmptyFull() { + LinkedBlockingDeque q = new LinkedBlockingDeque(2); + assertTrue(q.isEmpty()); + assertEquals("should have room for 2", 2, q.remainingCapacity()); + q.add(one); + assertFalse(q.isEmpty()); + q.add(two); + assertFalse(q.isEmpty()); + assertEquals(0, q.remainingCapacity()); + assertFalse(q.offer(three)); + } + + /** + * remainingCapacity decreases on add, increases on remove + */ + public void testRemainingCapacity() { + BlockingQueue q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.remainingCapacity()); + assertEquals(SIZE, q.size() + q.remainingCapacity()); + assertEquals(i, q.remove()); + } + for (int i = 0; i < SIZE; ++i) { + assertEquals(SIZE - i, q.remainingCapacity()); + assertEquals(SIZE, q.size() + q.remainingCapacity()); + assertTrue(q.add(i)); + } + } + + /** + * push(null) throws NPE + */ + public void testPushNull() { + LinkedBlockingDeque q = new LinkedBlockingDeque(1); + try { + q.push(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * push succeeds if not full; throws ISE if full + */ + public void testPush() { + LinkedBlockingDeque q = new LinkedBlockingDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + Integer x = new Integer(i); + q.push(x); + assertEquals(x, q.peek()); + } + assertEquals(0, q.remainingCapacity()); + try { + q.push(new Integer(SIZE)); + shouldThrow(); + } catch (IllegalStateException success) {} + } + + /** + * peekFirst returns element inserted with push + */ + public void testPushWithPeek() { + LinkedBlockingDeque q = populatedDeque(3); + q.pollLast(); + q.push(four); + assertSame(four, q.peekFirst()); + } + + /** + * pop removes next element, or throws NSEE if empty + */ + public void testPop() { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.pop()); + } + try { + q.pop(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * Offer succeeds if not full; fails if full + */ + public void testOffer() { + LinkedBlockingDeque q = new LinkedBlockingDeque(1); + assertTrue(q.offer(zero)); + assertFalse(q.offer(one)); + } + + /** + * add succeeds if not full; throws ISE if full + */ + public void testAdd() { + LinkedBlockingDeque q = new LinkedBlockingDeque(SIZE); + for (int i = 0; i < SIZE; ++i) + assertTrue(q.add(new Integer(i))); + assertEquals(0, q.remainingCapacity()); + try { + q.add(new Integer(SIZE)); + shouldThrow(); + } catch (IllegalStateException success) {} + } + + /** + * addAll(this) throws IAE + */ + public void testAddAllSelf() { + LinkedBlockingDeque q = populatedDeque(SIZE); + try { + q.addAll(q); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * addAll of a collection with any null elements throws NPE after + * possibly adding some elements + */ + public void testAddAll3() { + LinkedBlockingDeque q = new LinkedBlockingDeque(SIZE); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); + Collection elements = Arrays.asList(ints); + try { + q.addAll(elements); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll throws IllegalStateException if not enough room + */ + public void testAddAll4() { + LinkedBlockingDeque q = new LinkedBlockingDeque(SIZE - 1); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(i); + Collection elements = Arrays.asList(ints); + try { + q.addAll(elements); + shouldThrow(); + } catch (IllegalStateException success) {} + } + + /** + * Deque contains all elements, in traversal order, of successful addAll + */ + public void testAddAll5() { + Integer[] empty = new Integer[0]; + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(i); + LinkedBlockingDeque q = new LinkedBlockingDeque(SIZE); + assertFalse(q.addAll(Arrays.asList(empty))); + assertTrue(q.addAll(Arrays.asList(ints))); + for (int i = 0; i < SIZE; ++i) + assertEquals(ints[i], q.poll()); + } + + /** + * all elements successfully put are contained + */ + public void testPut() throws InterruptedException { + LinkedBlockingDeque q = new LinkedBlockingDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + Integer x = new Integer(i); + q.put(x); + assertTrue(q.contains(x)); + } + assertEquals(0, q.remainingCapacity()); + } + + /** + * put blocks interruptibly if full + */ + public void testBlockingPut() throws InterruptedException { + final LinkedBlockingDeque q = new LinkedBlockingDeque(SIZE); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + for (int i = 0; i < SIZE; ++i) + q.put(i); + assertEquals(SIZE, q.size()); + assertEquals(0, q.remainingCapacity()); + + Thread.currentThread().interrupt(); + try { + q.put(99); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + q.put(99); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + assertEquals(SIZE, q.size()); + assertEquals(0, q.remainingCapacity()); + } + + /** + * put blocks interruptibly waiting for take when full + */ + public void testPutWithTake() throws InterruptedException { + final int capacity = 2; + final LinkedBlockingDeque q = new LinkedBlockingDeque(capacity); + final CountDownLatch pleaseTake = new CountDownLatch(1); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + for (int i = 0; i < capacity; i++) + q.put(i); + pleaseTake.countDown(); + q.put(86); + + pleaseInterrupt.countDown(); + try { + q.put(99); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseTake); + assertEquals(0, q.remainingCapacity()); + assertEquals(0, q.take()); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + assertEquals(0, q.remainingCapacity()); + } + + /** + * timed offer times out if full and elements not taken + */ + public void testTimedOffer() throws InterruptedException { + final LinkedBlockingDeque q = new LinkedBlockingDeque(2); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + q.put(new Object()); + q.put(new Object()); + long startTime = System.nanoTime(); + assertFalse(q.offer(new Object(), timeoutMillis(), MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + pleaseInterrupt.countDown(); + try { + q.offer(new Object(), 2 * LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * take retrieves elements in FIFO order + */ + public void testTake() throws InterruptedException { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.take()); + } + } + + /** + * take removes existing elements until empty, then blocks interruptibly + */ + public void testBlockingTake() throws InterruptedException { + final LinkedBlockingDeque q = populatedDeque(SIZE); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.take()); + } + + Thread.currentThread().interrupt(); + try { + q.take(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + q.take(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * poll succeeds unless empty + */ + public void testPoll() { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.poll()); + } + assertNull(q.poll()); + } + + /** + * timed poll with zero timeout succeeds when non-empty, else times out + */ + public void testTimedPoll0() throws InterruptedException { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.poll(0, MILLISECONDS)); + } + assertNull(q.poll(0, MILLISECONDS)); + } + + /** + * timed poll with nonzero timeout succeeds when non-empty, else times out + */ + public void testTimedPoll() throws InterruptedException { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + long startTime = System.nanoTime(); + assertEquals(i, q.poll(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + long startTime = System.nanoTime(); + assertNull(q.poll(timeoutMillis(), MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + checkEmpty(q); + } + + /** + * Interrupted timed poll throws InterruptedException instead of + * returning timeout status + */ + public void testInterruptedTimedPoll() throws InterruptedException { + final BlockingQueue q = populatedDeque(SIZE); + final CountDownLatch aboutToWait = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, (int) q.poll(LONG_DELAY_MS, MILLISECONDS)); + } + aboutToWait.countDown(); + try { + q.poll(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) { + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + }}); + + aboutToWait.await(); + waitForThreadToEnterWaitState(t, LONG_DELAY_MS); + t.interrupt(); + awaitTermination(t); + checkEmpty(q); + } + + /** + * putFirst(null) throws NPE + */ + public void testPutFirstNull() throws InterruptedException { + LinkedBlockingDeque q = new LinkedBlockingDeque(SIZE); + try { + q.putFirst(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * all elements successfully putFirst are contained + */ + public void testPutFirst() throws InterruptedException { + LinkedBlockingDeque q = new LinkedBlockingDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + Integer x = new Integer(i); + q.putFirst(x); + assertTrue(q.contains(x)); + } + assertEquals(0, q.remainingCapacity()); + } + + /** + * putFirst blocks interruptibly if full + */ + public void testBlockingPutFirst() throws InterruptedException { + final LinkedBlockingDeque q = new LinkedBlockingDeque(SIZE); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + for (int i = 0; i < SIZE; ++i) + q.putFirst(i); + assertEquals(SIZE, q.size()); + assertEquals(0, q.remainingCapacity()); + + Thread.currentThread().interrupt(); + try { + q.putFirst(99); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + q.putFirst(99); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + assertEquals(SIZE, q.size()); + assertEquals(0, q.remainingCapacity()); + } + + /** + * putFirst blocks interruptibly waiting for take when full + */ + public void testPutFirstWithTake() throws InterruptedException { + final int capacity = 2; + final LinkedBlockingDeque q = new LinkedBlockingDeque(capacity); + final CountDownLatch pleaseTake = new CountDownLatch(1); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + for (int i = 0; i < capacity; i++) + q.putFirst(i); + pleaseTake.countDown(); + q.putFirst(86); + + pleaseInterrupt.countDown(); + try { + q.putFirst(99); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseTake); + assertEquals(0, q.remainingCapacity()); + assertEquals(capacity - 1, q.take()); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + assertEquals(0, q.remainingCapacity()); + } + + /** + * timed offerFirst times out if full and elements not taken + */ + public void testTimedOfferFirst() throws InterruptedException { + final LinkedBlockingDeque q = new LinkedBlockingDeque(2); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + q.putFirst(new Object()); + q.putFirst(new Object()); + long startTime = System.nanoTime(); + assertFalse(q.offerFirst(new Object(), timeoutMillis(), MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + pleaseInterrupt.countDown(); + try { + q.offerFirst(new Object(), 2 * LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * take retrieves elements in FIFO order + */ + public void testTakeFirst() throws InterruptedException { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.takeFirst()); + } + } + + /** + * takeFirst() blocks interruptibly when empty + */ + public void testTakeFirstFromEmptyBlocksInterruptibly() { + final BlockingDeque q = new LinkedBlockingDeque(); + final CountDownLatch threadStarted = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + threadStarted.countDown(); + try { + q.takeFirst(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(threadStarted); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * takeFirst() throws InterruptedException immediately if interrupted + * before waiting + */ + public void testTakeFirstFromEmptyAfterInterrupt() { + final BlockingDeque q = new LinkedBlockingDeque(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + Thread.currentThread().interrupt(); + try { + q.takeFirst(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + awaitTermination(t); + } + + /** + * takeLast() blocks interruptibly when empty + */ + public void testTakeLastFromEmptyBlocksInterruptibly() { + final BlockingDeque q = new LinkedBlockingDeque(); + final CountDownLatch threadStarted = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + threadStarted.countDown(); + try { + q.takeLast(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(threadStarted); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * takeLast() throws InterruptedException immediately if interrupted + * before waiting + */ + public void testTakeLastFromEmptyAfterInterrupt() { + final BlockingDeque q = new LinkedBlockingDeque(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + Thread.currentThread().interrupt(); + try { + q.takeLast(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + awaitTermination(t); + } + + /** + * takeFirst removes existing elements until empty, then blocks interruptibly + */ + public void testBlockingTakeFirst() throws InterruptedException { + final LinkedBlockingDeque q = populatedDeque(SIZE); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.takeFirst()); + } + + Thread.currentThread().interrupt(); + try { + q.takeFirst(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + q.takeFirst(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * timed pollFirst with zero timeout succeeds when non-empty, else times out + */ + public void testTimedPollFirst0() throws InterruptedException { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.pollFirst(0, MILLISECONDS)); + } + assertNull(q.pollFirst(0, MILLISECONDS)); + } + + /** + * timed pollFirst with nonzero timeout succeeds when non-empty, else times out + */ + public void testTimedPollFirst() throws InterruptedException { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + long startTime = System.nanoTime(); + assertEquals(i, q.pollFirst(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + long startTime = System.nanoTime(); + assertNull(q.pollFirst(timeoutMillis(), MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + checkEmpty(q); + } + + /** + * Interrupted timed pollFirst throws InterruptedException instead of + * returning timeout status + */ + public void testInterruptedTimedPollFirst() throws InterruptedException { + final LinkedBlockingDeque q = populatedDeque(SIZE); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.pollFirst(LONG_DELAY_MS, MILLISECONDS)); + } + + Thread.currentThread().interrupt(); + try { + q.pollFirst(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + q.pollFirst(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * timed pollFirst before a delayed offerFirst fails; after offerFirst succeeds; + * on interruption throws + */ + public void testTimedPollFirstWithOfferFirst() throws InterruptedException { + final LinkedBlockingDeque q = new LinkedBlockingDeque(2); + final CheckedBarrier barrier = new CheckedBarrier(2); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + assertNull(q.pollFirst(timeoutMillis(), MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + + barrier.await(); + + assertSame(zero, q.pollFirst(LONG_DELAY_MS, MILLISECONDS)); + + Thread.currentThread().interrupt(); + try { + q.pollFirst(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + + barrier.await(); + try { + q.pollFirst(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + }}); + + barrier.await(); + long startTime = System.nanoTime(); + assertTrue(q.offerFirst(zero, LONG_DELAY_MS, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + barrier.await(); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * putLast(null) throws NPE + */ + public void testPutLastNull() throws InterruptedException { + LinkedBlockingDeque q = new LinkedBlockingDeque(SIZE); + try { + q.putLast(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * all elements successfully putLast are contained + */ + public void testPutLast() throws InterruptedException { + LinkedBlockingDeque q = new LinkedBlockingDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + Integer x = new Integer(i); + q.putLast(x); + assertTrue(q.contains(x)); + } + assertEquals(0, q.remainingCapacity()); + } + + /** + * putLast blocks interruptibly if full + */ + public void testBlockingPutLast() throws InterruptedException { + final LinkedBlockingDeque q = new LinkedBlockingDeque(SIZE); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + for (int i = 0; i < SIZE; ++i) + q.putLast(i); + assertEquals(SIZE, q.size()); + assertEquals(0, q.remainingCapacity()); + + Thread.currentThread().interrupt(); + try { + q.putLast(99); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + q.putLast(99); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + assertEquals(SIZE, q.size()); + assertEquals(0, q.remainingCapacity()); + } + + /** + * putLast blocks interruptibly waiting for take when full + */ + public void testPutLastWithTake() throws InterruptedException { + final int capacity = 2; + final LinkedBlockingDeque q = new LinkedBlockingDeque(capacity); + final CountDownLatch pleaseTake = new CountDownLatch(1); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + for (int i = 0; i < capacity; i++) + q.putLast(i); + pleaseTake.countDown(); + q.putLast(86); + + pleaseInterrupt.countDown(); + try { + q.putLast(99); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseTake); + assertEquals(0, q.remainingCapacity()); + assertEquals(0, q.take()); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + assertEquals(0, q.remainingCapacity()); + } + + /** + * timed offerLast times out if full and elements not taken + */ + public void testTimedOfferLast() throws InterruptedException { + final LinkedBlockingDeque q = new LinkedBlockingDeque(2); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + q.putLast(new Object()); + q.putLast(new Object()); + long startTime = System.nanoTime(); + assertFalse(q.offerLast(new Object(), timeoutMillis(), MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + pleaseInterrupt.countDown(); + try { + q.offerLast(new Object(), 2 * LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * takeLast retrieves elements in FIFO order + */ + public void testTakeLast() throws InterruptedException { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(SIZE - i - 1, q.takeLast()); + } + } + + /** + * takeLast removes existing elements until empty, then blocks interruptibly + */ + public void testBlockingTakeLast() throws InterruptedException { + final LinkedBlockingDeque q = populatedDeque(SIZE); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + for (int i = 0; i < SIZE; ++i) { + assertEquals(SIZE - i - 1, q.takeLast()); + } + + Thread.currentThread().interrupt(); + try { + q.takeLast(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + q.takeLast(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * timed pollLast with zero timeout succeeds when non-empty, else times out + */ + public void testTimedPollLast0() throws InterruptedException { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(SIZE - i - 1, q.pollLast(0, MILLISECONDS)); + } + assertNull(q.pollLast(0, MILLISECONDS)); + } + + /** + * timed pollLast with nonzero timeout succeeds when non-empty, else times out + */ + public void testTimedPollLast() throws InterruptedException { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + long startTime = System.nanoTime(); + assertEquals(SIZE - i - 1, q.pollLast(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + long startTime = System.nanoTime(); + assertNull(q.pollLast(timeoutMillis(), MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + checkEmpty(q); + } + + /** + * Interrupted timed pollLast throws InterruptedException instead of + * returning timeout status + */ + public void testInterruptedTimedPollLast() throws InterruptedException { + final LinkedBlockingDeque q = populatedDeque(SIZE); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + for (int i = 0; i < SIZE; ++i) { + assertEquals(SIZE - i - 1, + q.pollLast(LONG_DELAY_MS, MILLISECONDS)); + } + + Thread.currentThread().interrupt(); + try { + q.pollLast(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + q.pollLast(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + checkEmpty(q); + } + + /** + * timed poll before a delayed offerLast fails; after offerLast succeeds; + * on interruption throws + */ + public void testTimedPollWithOfferLast() throws InterruptedException { + final LinkedBlockingDeque q = new LinkedBlockingDeque(2); + final CheckedBarrier barrier = new CheckedBarrier(2); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + assertNull(q.poll(timeoutMillis(), MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + + barrier.await(); + + assertSame(zero, q.poll(LONG_DELAY_MS, MILLISECONDS)); + + Thread.currentThread().interrupt(); + try { + q.poll(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + barrier.await(); + try { + q.poll(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + }}); + + barrier.await(); + long startTime = System.nanoTime(); + assertTrue(q.offerLast(zero, LONG_DELAY_MS, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + + barrier.await(); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * element returns next element, or throws NSEE if empty + */ + public void testElement() { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.element()); + q.poll(); + } + try { + q.element(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * contains(x) reports true when elements added but not yet removed + */ + public void testContains() { + LinkedBlockingDeque q = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.contains(new Integer(i))); + q.poll(); + assertFalse(q.contains(new Integer(i))); + } + } + + /** + * clear removes all elements + */ + public void testClear() { + LinkedBlockingDeque q = populatedDeque(SIZE); + q.clear(); + assertTrue(q.isEmpty()); + assertEquals(0, q.size()); + assertEquals(SIZE, q.remainingCapacity()); + q.add(one); + assertFalse(q.isEmpty()); + assertTrue(q.contains(one)); + q.clear(); + assertTrue(q.isEmpty()); + } + + /** + * containsAll(c) is true when c contains a subset of elements + */ + public void testContainsAll() { + LinkedBlockingDeque q = populatedDeque(SIZE); + LinkedBlockingDeque p = new LinkedBlockingDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.containsAll(p)); + assertFalse(p.containsAll(q)); + p.add(new Integer(i)); + } + assertTrue(p.containsAll(q)); + } + + /** + * retainAll(c) retains only those elements of c and reports true if changed + */ + public void testRetainAll() { + LinkedBlockingDeque q = populatedDeque(SIZE); + LinkedBlockingDeque p = populatedDeque(SIZE); + for (int i = 0; i < SIZE; ++i) { + boolean changed = q.retainAll(p); + if (i == 0) + assertFalse(changed); + else + assertTrue(changed); + + assertTrue(q.containsAll(p)); + assertEquals(SIZE - i, q.size()); + p.remove(); + } + } + + /** + * removeAll(c) removes only those elements of c and reports true if changed + */ + public void testRemoveAll() { + for (int i = 1; i < SIZE; ++i) { + LinkedBlockingDeque q = populatedDeque(SIZE); + LinkedBlockingDeque p = populatedDeque(i); + assertTrue(q.removeAll(p)); + assertEquals(SIZE - i, q.size()); + for (int j = 0; j < i; ++j) { + Integer x = (Integer)(p.remove()); + assertFalse(q.contains(x)); + } + } + } + + /** + * toArray contains all elements in FIFO order + */ + public void testToArray() throws InterruptedException { + LinkedBlockingDeque q = populatedDeque(SIZE); + Object[] o = q.toArray(); + for (int i = 0; i < o.length; i++) + assertSame(o[i], q.poll()); + } + + /** + * toArray(a) contains all elements in FIFO order + */ + public void testToArray2() { + LinkedBlockingDeque q = populatedDeque(SIZE); + Integer[] ints = new Integer[SIZE]; + Integer[] array = q.toArray(ints); + assertSame(ints, array); + for (int i = 0; i < ints.length; i++) + assertSame(ints[i], q.remove()); + } + + /** + * toArray(incompatible array type) throws ArrayStoreException + */ + public void testToArray1_BadArg() { + LinkedBlockingDeque q = populatedDeque(SIZE); + try { + q.toArray(new String[10]); + shouldThrow(); + } catch (ArrayStoreException success) {} + } + + /** + * iterator iterates through all elements + */ + public void testIterator() throws InterruptedException { + LinkedBlockingDeque q = populatedDeque(SIZE); + Iterator it = q.iterator(); + int i; + for (i = 0; it.hasNext(); i++) + assertTrue(q.contains(it.next())); + assertEquals(i, SIZE); + assertIteratorExhausted(it); + + it = q.iterator(); + for (i = 0; it.hasNext(); i++) + assertEquals(it.next(), q.take()); + assertEquals(i, SIZE); + assertIteratorExhausted(it); + } + + /** + * iterator of empty collection has no elements + */ + public void testEmptyIterator() { + Deque c = new LinkedBlockingDeque(); + assertIteratorExhausted(c.iterator()); + assertIteratorExhausted(c.descendingIterator()); + } + + /** + * iterator.remove removes current element + */ + public void testIteratorRemove() { + final LinkedBlockingDeque q = new LinkedBlockingDeque(3); + q.add(two); + q.add(one); + q.add(three); + + Iterator it = q.iterator(); + it.next(); + it.remove(); + + it = q.iterator(); + assertSame(it.next(), one); + assertSame(it.next(), three); + assertFalse(it.hasNext()); + } + + /** + * iterator ordering is FIFO + */ + public void testIteratorOrdering() { + final LinkedBlockingDeque q = new LinkedBlockingDeque(3); + q.add(one); + q.add(two); + q.add(three); + assertEquals(0, q.remainingCapacity()); + int k = 0; + for (Iterator it = q.iterator(); it.hasNext();) { + assertEquals(++k, it.next()); + } + assertEquals(3, k); + } + + /** + * Modifications do not cause iterators to fail + */ + public void testWeaklyConsistentIteration() { + final LinkedBlockingDeque q = new LinkedBlockingDeque(3); + q.add(one); + q.add(two); + q.add(three); + for (Iterator it = q.iterator(); it.hasNext();) { + q.remove(); + it.next(); + } + assertEquals(0, q.size()); + } + + /** + * Descending iterator iterates through all elements + */ + public void testDescendingIterator() { + LinkedBlockingDeque q = populatedDeque(SIZE); + int i = 0; + Iterator it = q.descendingIterator(); + while (it.hasNext()) { + assertTrue(q.contains(it.next())); + ++i; + } + assertEquals(i, SIZE); + assertFalse(it.hasNext()); + try { + it.next(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * Descending iterator ordering is reverse FIFO + */ + public void testDescendingIteratorOrdering() { + final LinkedBlockingDeque q = new LinkedBlockingDeque(); + for (int iters = 0; iters < 100; ++iters) { + q.add(new Integer(3)); + q.add(new Integer(2)); + q.add(new Integer(1)); + int k = 0; + for (Iterator it = q.descendingIterator(); it.hasNext();) { + assertEquals(++k, it.next()); + } + + assertEquals(3, k); + q.remove(); + q.remove(); + q.remove(); + } + } + + /** + * descendingIterator.remove removes current element + */ + public void testDescendingIteratorRemove() { + final LinkedBlockingDeque q = new LinkedBlockingDeque(); + for (int iters = 0; iters < 100; ++iters) { + q.add(new Integer(3)); + q.add(new Integer(2)); + q.add(new Integer(1)); + Iterator it = q.descendingIterator(); + assertEquals(it.next(), new Integer(1)); + it.remove(); + assertEquals(it.next(), new Integer(2)); + it = q.descendingIterator(); + assertEquals(it.next(), new Integer(2)); + assertEquals(it.next(), new Integer(3)); + it.remove(); + assertFalse(it.hasNext()); + q.remove(); + } + } + + /** + * toString contains toStrings of elements + */ + public void testToString() { + LinkedBlockingDeque q = populatedDeque(SIZE); + String s = q.toString(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(s.contains(String.valueOf(i))); + } + } + + /** + * offer transfers elements across Executor tasks + */ + public void testOfferInExecutor() { + final LinkedBlockingDeque q = new LinkedBlockingDeque(2); + q.add(one); + q.add(two); + final CheckedBarrier threadsStarted = new CheckedBarrier(2); + final ExecutorService executor = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(executor)) { + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(q.offer(three)); + threadsStarted.await(); + assertTrue(q.offer(three, LONG_DELAY_MS, MILLISECONDS)); + assertEquals(0, q.remainingCapacity()); + }}); + + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + assertSame(one, q.take()); + }}); + } + } + + /** + * timed poll retrieves elements across Executor threads + */ + public void testPollInExecutor() { + final LinkedBlockingDeque q = new LinkedBlockingDeque(2); + final CheckedBarrier threadsStarted = new CheckedBarrier(2); + final ExecutorService executor = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(executor)) { + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertNull(q.poll()); + threadsStarted.await(); + assertSame(one, q.poll(LONG_DELAY_MS, MILLISECONDS)); + checkEmpty(q); + }}); + + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + q.put(one); + }}); + } + } + + /** + * A deserialized serialized deque has same elements in same order + */ + public void testSerialization() throws Exception { + Queue x = populatedDeque(SIZE); + Queue y = serialClone(x); + + assertNotSame(y, x); + assertEquals(x.size(), y.size()); + assertEquals(x.toString(), y.toString()); + assertTrue(Arrays.equals(x.toArray(), y.toArray())); + while (!x.isEmpty()) { + assertFalse(y.isEmpty()); + assertEquals(x.remove(), y.remove()); + } + assertTrue(y.isEmpty()); + } + + /** + * drainTo(c) empties deque into another collection c + */ + public void testDrainTo() { + LinkedBlockingDeque q = populatedDeque(SIZE); + ArrayList l = new ArrayList(); + q.drainTo(l); + assertEquals(0, q.size()); + assertEquals(SIZE, l.size()); + for (int i = 0; i < SIZE; ++i) + assertEquals(l.get(i), new Integer(i)); + q.add(zero); + q.add(one); + assertFalse(q.isEmpty()); + assertTrue(q.contains(zero)); + assertTrue(q.contains(one)); + l.clear(); + q.drainTo(l); + assertEquals(0, q.size()); + assertEquals(2, l.size()); + for (int i = 0; i < 2; ++i) + assertEquals(l.get(i), new Integer(i)); + } + + /** + * drainTo empties full deque, unblocking a waiting put. + */ + public void testDrainToWithActivePut() throws InterruptedException { + final LinkedBlockingDeque q = populatedDeque(SIZE); + Thread t = new Thread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + q.put(new Integer(SIZE + 1)); + }}); + + t.start(); + ArrayList l = new ArrayList(); + q.drainTo(l); + assertTrue(l.size() >= SIZE); + for (int i = 0; i < SIZE; ++i) + assertEquals(l.get(i), new Integer(i)); + t.join(); + assertTrue(q.size() + l.size() >= SIZE); + } + + /** + * drainTo(c, n) empties first min(n, size) elements of queue into c + */ + public void testDrainToN() { + LinkedBlockingDeque q = new LinkedBlockingDeque(); + for (int i = 0; i < SIZE + 2; ++i) { + for (int j = 0; j < SIZE; j++) + assertTrue(q.offer(new Integer(j))); + ArrayList l = new ArrayList(); + q.drainTo(l, i); + int k = (i < SIZE) ? i : SIZE; + assertEquals(k, l.size()); + assertEquals(SIZE - k, q.size()); + for (int j = 0; j < k; ++j) + assertEquals(l.get(j), new Integer(j)); + do {} while (q.poll() != null); + } + } + + /** + * remove(null), contains(null) always return false + */ + public void testNeverContainsNull() { + Deque[] qs = { + new LinkedBlockingDeque(), + populatedDeque(2), + }; + + for (Deque q : qs) { + assertFalse(q.contains(null)); + assertFalse(q.remove(null)); + assertFalse(q.removeFirstOccurrence(null)); + assertFalse(q.removeLastOccurrence(null)); + } + } + +} diff --git a/jdk/test/java/util/concurrent/tck/LinkedBlockingQueueTest.java b/jdk/test/java/util/concurrent/tck/LinkedBlockingQueueTest.java new file mode 100644 index 00000000000..51246e903a6 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/LinkedBlockingQueueTest.java @@ -0,0 +1,889 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; + +import junit.framework.Test; + +public class LinkedBlockingQueueTest extends JSR166TestCase { + + public static class Unbounded extends BlockingQueueTest { + protected BlockingQueue emptyCollection() { + return new LinkedBlockingQueue(); + } + } + + public static class Bounded extends BlockingQueueTest { + protected BlockingQueue emptyCollection() { + return new LinkedBlockingQueue(SIZE); + } + } + + public static void main(String[] args) { + main(suite(), args); + } + + public static Test suite() { + return newTestSuite(LinkedBlockingQueueTest.class, + new Unbounded().testSuite(), + new Bounded().testSuite()); + } + + /** + * Returns a new queue of given size containing consecutive + * Integers 0 ... n. + */ + private LinkedBlockingQueue populatedQueue(int n) { + LinkedBlockingQueue q = + new LinkedBlockingQueue(n); + assertTrue(q.isEmpty()); + for (int i = 0; i < n; i++) + assertTrue(q.offer(new Integer(i))); + assertFalse(q.isEmpty()); + assertEquals(0, q.remainingCapacity()); + assertEquals(n, q.size()); + return q; + } + + /** + * A new queue has the indicated capacity, or Integer.MAX_VALUE if + * none given + */ + public void testConstructor1() { + assertEquals(SIZE, new LinkedBlockingQueue(SIZE).remainingCapacity()); + assertEquals(Integer.MAX_VALUE, new LinkedBlockingQueue().remainingCapacity()); + } + + /** + * Constructor throws IllegalArgumentException if capacity argument nonpositive + */ + public void testConstructor2() { + try { + new LinkedBlockingQueue(0); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Initializing from null Collection throws NullPointerException + */ + public void testConstructor3() { + try { + new LinkedBlockingQueue(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing from Collection of null elements throws NullPointerException + */ + public void testConstructor4() { + Collection elements = Arrays.asList(new Integer[SIZE]); + try { + new LinkedBlockingQueue(elements); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing from Collection with some null elements throws + * NullPointerException + */ + public void testConstructor5() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); + Collection elements = Arrays.asList(ints); + try { + new LinkedBlockingQueue(elements); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Queue contains all elements of collection used to initialize + */ + public void testConstructor6() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(i); + LinkedBlockingQueue q = new LinkedBlockingQueue(Arrays.asList(ints)); + for (int i = 0; i < SIZE; ++i) + assertEquals(ints[i], q.poll()); + } + + /** + * Queue transitions from empty to full when elements added + */ + public void testEmptyFull() { + LinkedBlockingQueue q = new LinkedBlockingQueue(2); + assertTrue(q.isEmpty()); + assertEquals("should have room for 2", 2, q.remainingCapacity()); + q.add(one); + assertFalse(q.isEmpty()); + q.add(two); + assertFalse(q.isEmpty()); + assertEquals(0, q.remainingCapacity()); + assertFalse(q.offer(three)); + } + + /** + * remainingCapacity decreases on add, increases on remove + */ + public void testRemainingCapacity() { + BlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.remainingCapacity()); + assertEquals(SIZE, q.size() + q.remainingCapacity()); + assertEquals(i, q.remove()); + } + for (int i = 0; i < SIZE; ++i) { + assertEquals(SIZE - i, q.remainingCapacity()); + assertEquals(SIZE, q.size() + q.remainingCapacity()); + assertTrue(q.add(i)); + } + } + + /** + * Offer succeeds if not full; fails if full + */ + public void testOffer() { + LinkedBlockingQueue q = new LinkedBlockingQueue(1); + assertTrue(q.offer(zero)); + assertFalse(q.offer(one)); + } + + /** + * add succeeds if not full; throws IllegalStateException if full + */ + public void testAdd() { + LinkedBlockingQueue q = new LinkedBlockingQueue(SIZE); + for (int i = 0; i < SIZE; ++i) + assertTrue(q.add(new Integer(i))); + assertEquals(0, q.remainingCapacity()); + try { + q.add(new Integer(SIZE)); + shouldThrow(); + } catch (IllegalStateException success) {} + } + + /** + * addAll(this) throws IllegalArgumentException + */ + public void testAddAllSelf() { + LinkedBlockingQueue q = populatedQueue(SIZE); + try { + q.addAll(q); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * addAll of a collection with any null elements throws NPE after + * possibly adding some elements + */ + public void testAddAll3() { + LinkedBlockingQueue q = new LinkedBlockingQueue(SIZE); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); + Collection elements = Arrays.asList(ints); + try { + q.addAll(elements); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll throws IllegalStateException if not enough room + */ + public void testAddAll4() { + LinkedBlockingQueue q = new LinkedBlockingQueue(SIZE - 1); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(i); + Collection elements = Arrays.asList(ints); + try { + q.addAll(elements); + shouldThrow(); + } catch (IllegalStateException success) {} + } + + /** + * Queue contains all elements, in traversal order, of successful addAll + */ + public void testAddAll5() { + Integer[] empty = new Integer[0]; + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(i); + LinkedBlockingQueue q = new LinkedBlockingQueue(SIZE); + assertFalse(q.addAll(Arrays.asList(empty))); + assertTrue(q.addAll(Arrays.asList(ints))); + for (int i = 0; i < SIZE; ++i) + assertEquals(ints[i], q.poll()); + } + + /** + * all elements successfully put are contained + */ + public void testPut() throws InterruptedException { + LinkedBlockingQueue q = new LinkedBlockingQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + Integer x = new Integer(i); + q.put(x); + assertTrue(q.contains(x)); + } + assertEquals(0, q.remainingCapacity()); + } + + /** + * put blocks interruptibly if full + */ + public void testBlockingPut() throws InterruptedException { + final LinkedBlockingQueue q = new LinkedBlockingQueue(SIZE); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + for (int i = 0; i < SIZE; ++i) + q.put(i); + assertEquals(SIZE, q.size()); + assertEquals(0, q.remainingCapacity()); + + Thread.currentThread().interrupt(); + try { + q.put(99); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + q.put(99); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + assertEquals(SIZE, q.size()); + assertEquals(0, q.remainingCapacity()); + } + + /** + * put blocks interruptibly waiting for take when full + */ + public void testPutWithTake() throws InterruptedException { + final int capacity = 2; + final LinkedBlockingQueue q = new LinkedBlockingQueue(2); + final CountDownLatch pleaseTake = new CountDownLatch(1); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + for (int i = 0; i < capacity; i++) + q.put(i); + pleaseTake.countDown(); + q.put(86); + + pleaseInterrupt.countDown(); + try { + q.put(99); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseTake); + assertEquals(0, q.remainingCapacity()); + assertEquals(0, q.take()); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + assertEquals(0, q.remainingCapacity()); + } + + /** + * timed offer times out if full and elements not taken + */ + public void testTimedOffer() { + final LinkedBlockingQueue q = new LinkedBlockingQueue(2); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + q.put(new Object()); + q.put(new Object()); + long startTime = System.nanoTime(); + assertFalse(q.offer(new Object(), timeoutMillis(), MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + pleaseInterrupt.countDown(); + try { + q.offer(new Object(), 2 * LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * take retrieves elements in FIFO order + */ + public void testTake() throws InterruptedException { + LinkedBlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.take()); + } + } + + /** + * Take removes existing elements until empty, then blocks interruptibly + */ + public void testBlockingTake() throws InterruptedException { + final BlockingQueue q = populatedQueue(SIZE); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.take()); + } + + Thread.currentThread().interrupt(); + try { + q.take(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + q.take(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * poll succeeds unless empty + */ + public void testPoll() { + LinkedBlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.poll()); + } + assertNull(q.poll()); + } + + /** + * timed poll with zero timeout succeeds when non-empty, else times out + */ + public void testTimedPoll0() throws InterruptedException { + LinkedBlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.poll(0, MILLISECONDS)); + } + assertNull(q.poll(0, MILLISECONDS)); + } + + /** + * timed poll with nonzero timeout succeeds when non-empty, else times out + */ + public void testTimedPoll() throws InterruptedException { + LinkedBlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + long startTime = System.nanoTime(); + assertEquals(i, (int) q.poll(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + long startTime = System.nanoTime(); + assertNull(q.poll(timeoutMillis(), MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + checkEmpty(q); + } + + /** + * Interrupted timed poll throws InterruptedException instead of + * returning timeout status + */ + public void testInterruptedTimedPoll() throws InterruptedException { + final BlockingQueue q = populatedQueue(SIZE); + final CountDownLatch aboutToWait = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, (int) q.poll(LONG_DELAY_MS, MILLISECONDS)); + } + aboutToWait.countDown(); + try { + q.poll(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) { + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + }}); + + await(aboutToWait); + waitForThreadToEnterWaitState(t, LONG_DELAY_MS); + t.interrupt(); + awaitTermination(t); + checkEmpty(q); + } + + /** + * peek returns next element, or null if empty + */ + public void testPeek() { + LinkedBlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.peek()); + assertEquals(i, q.poll()); + assertTrue(q.peek() == null || + !q.peek().equals(i)); + } + assertNull(q.peek()); + } + + /** + * element returns next element, or throws NSEE if empty + */ + public void testElement() { + LinkedBlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.element()); + assertEquals(i, q.poll()); + } + try { + q.element(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * remove removes next element, or throws NSEE if empty + */ + public void testRemove() { + LinkedBlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.remove()); + } + try { + q.remove(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * An add following remove(x) succeeds + */ + public void testRemoveElementAndAdd() throws InterruptedException { + LinkedBlockingQueue q = new LinkedBlockingQueue(); + assertTrue(q.add(new Integer(1))); + assertTrue(q.add(new Integer(2))); + assertTrue(q.remove(new Integer(1))); + assertTrue(q.remove(new Integer(2))); + assertTrue(q.add(new Integer(3))); + assertNotNull(q.take()); + } + + /** + * contains(x) reports true when elements added but not yet removed + */ + public void testContains() { + LinkedBlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.contains(new Integer(i))); + q.poll(); + assertFalse(q.contains(new Integer(i))); + } + } + + /** + * clear removes all elements + */ + public void testClear() { + LinkedBlockingQueue q = populatedQueue(SIZE); + q.clear(); + assertTrue(q.isEmpty()); + assertEquals(0, q.size()); + assertEquals(SIZE, q.remainingCapacity()); + q.add(one); + assertFalse(q.isEmpty()); + assertTrue(q.contains(one)); + q.clear(); + assertTrue(q.isEmpty()); + } + + /** + * containsAll(c) is true when c contains a subset of elements + */ + public void testContainsAll() { + LinkedBlockingQueue q = populatedQueue(SIZE); + LinkedBlockingQueue p = new LinkedBlockingQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.containsAll(p)); + assertFalse(p.containsAll(q)); + p.add(new Integer(i)); + } + assertTrue(p.containsAll(q)); + } + + /** + * retainAll(c) retains only those elements of c and reports true if changed + */ + public void testRetainAll() { + LinkedBlockingQueue q = populatedQueue(SIZE); + LinkedBlockingQueue p = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + boolean changed = q.retainAll(p); + if (i == 0) + assertFalse(changed); + else + assertTrue(changed); + + assertTrue(q.containsAll(p)); + assertEquals(SIZE - i, q.size()); + p.remove(); + } + } + + /** + * removeAll(c) removes only those elements of c and reports true if changed + */ + public void testRemoveAll() { + for (int i = 1; i < SIZE; ++i) { + LinkedBlockingQueue q = populatedQueue(SIZE); + LinkedBlockingQueue p = populatedQueue(i); + assertTrue(q.removeAll(p)); + assertEquals(SIZE - i, q.size()); + for (int j = 0; j < i; ++j) { + Integer x = (Integer)(p.remove()); + assertFalse(q.contains(x)); + } + } + } + + /** + * toArray contains all elements in FIFO order + */ + public void testToArray() { + LinkedBlockingQueue q = populatedQueue(SIZE); + Object[] o = q.toArray(); + for (int i = 0; i < o.length; i++) + assertSame(o[i], q.poll()); + } + + /** + * toArray(a) contains all elements in FIFO order + */ + public void testToArray2() throws InterruptedException { + LinkedBlockingQueue q = populatedQueue(SIZE); + Integer[] ints = new Integer[SIZE]; + Integer[] array = q.toArray(ints); + assertSame(ints, array); + for (int i = 0; i < ints.length; i++) + assertSame(ints[i], q.poll()); + } + + /** + * toArray(incompatible array type) throws ArrayStoreException + */ + public void testToArray1_BadArg() { + LinkedBlockingQueue q = populatedQueue(SIZE); + try { + q.toArray(new String[10]); + shouldThrow(); + } catch (ArrayStoreException success) {} + } + + /** + * iterator iterates through all elements + */ + public void testIterator() throws InterruptedException { + LinkedBlockingQueue q = populatedQueue(SIZE); + Iterator it = q.iterator(); + int i; + for (i = 0; it.hasNext(); i++) + assertTrue(q.contains(it.next())); + assertEquals(i, SIZE); + assertIteratorExhausted(it); + + it = q.iterator(); + for (i = 0; it.hasNext(); i++) + assertEquals(it.next(), q.take()); + assertEquals(i, SIZE); + assertIteratorExhausted(it); + } + + /** + * iterator of empty collection has no elements + */ + public void testEmptyIterator() { + assertIteratorExhausted(new LinkedBlockingQueue().iterator()); + } + + /** + * iterator.remove removes current element + */ + public void testIteratorRemove() { + final LinkedBlockingQueue q = new LinkedBlockingQueue(3); + q.add(two); + q.add(one); + q.add(three); + + Iterator it = q.iterator(); + it.next(); + it.remove(); + + it = q.iterator(); + assertSame(it.next(), one); + assertSame(it.next(), three); + assertFalse(it.hasNext()); + } + + /** + * iterator ordering is FIFO + */ + public void testIteratorOrdering() { + final LinkedBlockingQueue q = new LinkedBlockingQueue(3); + q.add(one); + q.add(two); + q.add(three); + assertEquals(0, q.remainingCapacity()); + int k = 0; + for (Iterator it = q.iterator(); it.hasNext();) { + assertEquals(++k, it.next()); + } + assertEquals(3, k); + } + + /** + * Modifications do not cause iterators to fail + */ + public void testWeaklyConsistentIteration() { + final LinkedBlockingQueue q = new LinkedBlockingQueue(3); + q.add(one); + q.add(two); + q.add(three); + for (Iterator it = q.iterator(); it.hasNext();) { + q.remove(); + it.next(); + } + assertEquals(0, q.size()); + } + + /** + * toString contains toStrings of elements + */ + public void testToString() { + LinkedBlockingQueue q = populatedQueue(SIZE); + String s = q.toString(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(s.contains(String.valueOf(i))); + } + } + + /** + * offer transfers elements across Executor tasks + */ + public void testOfferInExecutor() { + final LinkedBlockingQueue q = new LinkedBlockingQueue(2); + q.add(one); + q.add(two); + final CheckedBarrier threadsStarted = new CheckedBarrier(2); + final ExecutorService executor = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(executor)) { + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(q.offer(three)); + threadsStarted.await(); + assertTrue(q.offer(three, LONG_DELAY_MS, MILLISECONDS)); + assertEquals(0, q.remainingCapacity()); + }}); + + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + assertSame(one, q.take()); + }}); + } + } + + /** + * timed poll retrieves elements across Executor threads + */ + public void testPollInExecutor() { + final LinkedBlockingQueue q = new LinkedBlockingQueue(2); + final CheckedBarrier threadsStarted = new CheckedBarrier(2); + final ExecutorService executor = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(executor)) { + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertNull(q.poll()); + threadsStarted.await(); + assertSame(one, q.poll(LONG_DELAY_MS, MILLISECONDS)); + checkEmpty(q); + }}); + + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + q.put(one); + }}); + } + } + + /** + * A deserialized serialized queue has same elements in same order + */ + public void testSerialization() throws Exception { + Queue x = populatedQueue(SIZE); + Queue y = serialClone(x); + + assertNotSame(x, y); + assertEquals(x.size(), y.size()); + assertEquals(x.toString(), y.toString()); + assertTrue(Arrays.equals(x.toArray(), y.toArray())); + while (!x.isEmpty()) { + assertFalse(y.isEmpty()); + assertEquals(x.remove(), y.remove()); + } + assertTrue(y.isEmpty()); + } + + /** + * drainTo(c) empties queue into another collection c + */ + public void testDrainTo() { + LinkedBlockingQueue q = populatedQueue(SIZE); + ArrayList l = new ArrayList(); + q.drainTo(l); + assertEquals(0, q.size()); + assertEquals(SIZE, l.size()); + for (int i = 0; i < SIZE; ++i) + assertEquals(l.get(i), new Integer(i)); + q.add(zero); + q.add(one); + assertFalse(q.isEmpty()); + assertTrue(q.contains(zero)); + assertTrue(q.contains(one)); + l.clear(); + q.drainTo(l); + assertEquals(0, q.size()); + assertEquals(2, l.size()); + for (int i = 0; i < 2; ++i) + assertEquals(l.get(i), new Integer(i)); + } + + /** + * drainTo empties full queue, unblocking a waiting put. + */ + public void testDrainToWithActivePut() throws InterruptedException { + final LinkedBlockingQueue q = populatedQueue(SIZE); + Thread t = new Thread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + q.put(new Integer(SIZE + 1)); + }}); + + t.start(); + ArrayList l = new ArrayList(); + q.drainTo(l); + assertTrue(l.size() >= SIZE); + for (int i = 0; i < SIZE; ++i) + assertEquals(l.get(i), new Integer(i)); + t.join(); + assertTrue(q.size() + l.size() >= SIZE); + } + + /** + * drainTo(c, n) empties first min(n, size) elements of queue into c + */ + public void testDrainToN() { + LinkedBlockingQueue q = new LinkedBlockingQueue(); + for (int i = 0; i < SIZE + 2; ++i) { + for (int j = 0; j < SIZE; j++) + assertTrue(q.offer(new Integer(j))); + ArrayList l = new ArrayList(); + q.drainTo(l, i); + int k = (i < SIZE) ? i : SIZE; + assertEquals(k, l.size()); + assertEquals(SIZE - k, q.size()); + for (int j = 0; j < k; ++j) + assertEquals(l.get(j), new Integer(j)); + do {} while (q.poll() != null); + } + } + + /** + * remove(null), contains(null) always return false + */ + public void testNeverContainsNull() { + Collection[] qs = { + new LinkedBlockingQueue(), + populatedQueue(2), + }; + + for (Collection q : qs) { + assertFalse(q.contains(null)); + assertFalse(q.remove(null)); + } + } + +} diff --git a/jdk/test/java/util/concurrent/tck/LinkedListTest.java b/jdk/test/java/util/concurrent/tck/LinkedListTest.java new file mode 100644 index 00000000000..ea0f689dc70 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/LinkedListTest.java @@ -0,0 +1,669 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.NoSuchElementException; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class LinkedListTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + + public static Test suite() { + return new TestSuite(LinkedListTest.class); + } + + /** + * Returns a new queue of given size containing consecutive + * Integers 0 ... n. + */ + private LinkedList populatedQueue(int n) { + LinkedList q = new LinkedList(); + assertTrue(q.isEmpty()); + for (int i = 0; i < n; ++i) + assertTrue(q.offer(new Integer(i))); + assertFalse(q.isEmpty()); + assertEquals(n, q.size()); + return q; + } + + /** + * new queue is empty + */ + public void testConstructor1() { + assertEquals(0, new LinkedList().size()); + } + + /** + * Initializing from null Collection throws NPE + */ + public void testConstructor3() { + try { + new LinkedList((Collection)null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Queue contains all elements of collection used to initialize + */ + public void testConstructor6() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = i; + LinkedList q = new LinkedList(Arrays.asList(ints)); + for (int i = 0; i < SIZE; ++i) + assertEquals(ints[i], q.poll()); + } + + /** + * isEmpty is true before add, false after + */ + public void testEmpty() { + LinkedList q = new LinkedList(); + assertTrue(q.isEmpty()); + q.add(new Integer(1)); + assertFalse(q.isEmpty()); + q.add(new Integer(2)); + q.remove(); + q.remove(); + assertTrue(q.isEmpty()); + } + + /** + * size changes when elements added and removed + */ + public void testSize() { + LinkedList q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(SIZE - i, q.size()); + q.remove(); + } + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.size()); + q.add(new Integer(i)); + } + } + + /** + * offer(null) succeeds + */ + public void testOfferNull() { + LinkedList q = new LinkedList(); + q.offer(null); + assertNull(q.get(0)); + assertTrue(q.contains(null)); + } + + /** + * Offer succeeds + */ + public void testOffer() { + LinkedList q = new LinkedList(); + assertTrue(q.offer(new Integer(0))); + assertTrue(q.offer(new Integer(1))); + } + + /** + * add succeeds + */ + public void testAdd() { + LinkedList q = new LinkedList(); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.size()); + assertTrue(q.add(new Integer(i))); + } + } + + /** + * addAll(null) throws NPE + */ + public void testAddAll1() { + LinkedList q = new LinkedList(); + try { + q.addAll(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Queue contains all elements, in traversal order, of successful addAll + */ + public void testAddAll5() { + Integer[] empty = new Integer[0]; + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = i; + LinkedList q = new LinkedList(); + assertFalse(q.addAll(Arrays.asList(empty))); + assertTrue(q.addAll(Arrays.asList(ints))); + for (int i = 0; i < SIZE; ++i) + assertEquals(ints[i], q.poll()); + } + + /** + * addAll with too large an index throws IOOBE + */ + public void testAddAll2_IndexOutOfBoundsException() { + LinkedList l = new LinkedList(); + l.add(new Object()); + LinkedList m = new LinkedList(); + m.add(new Object()); + try { + l.addAll(4,m); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + } + + /** + * addAll with negative index throws IOOBE + */ + public void testAddAll4_BadIndex() { + LinkedList l = new LinkedList(); + l.add(new Object()); + LinkedList m = new LinkedList(); + m.add(new Object()); + try { + l.addAll(-1,m); + shouldThrow(); + } catch (IndexOutOfBoundsException success) {} + } + + /** + * poll succeeds unless empty + */ + public void testPoll() { + LinkedList q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.poll()); + } + assertNull(q.poll()); + } + + /** + * peek returns next element, or null if empty + */ + public void testPeek() { + LinkedList q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.peek()); + assertEquals(i, q.poll()); + assertTrue(q.peek() == null || + !q.peek().equals(i)); + } + assertNull(q.peek()); + } + + /** + * element returns next element, or throws NSEE if empty + */ + public void testElement() { + LinkedList q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.element()); + assertEquals(i, q.poll()); + } + try { + q.element(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * remove removes next element, or throws NSEE if empty + */ + public void testRemove() { + LinkedList q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.remove()); + } + try { + q.remove(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * remove(x) removes x and returns true if present + */ + public void testRemoveElement() { + LinkedList q = populatedQueue(SIZE); + for (int i = 1; i < SIZE; i += 2) { + assertTrue(q.contains(i)); + assertTrue(q.remove((Integer)i)); + assertFalse(q.contains(i)); + assertTrue(q.contains(i - 1)); + } + for (int i = 0; i < SIZE; i += 2) { + assertTrue(q.contains(i)); + assertTrue(q.remove((Integer)i)); + assertFalse(q.contains(i)); + assertFalse(q.remove((Integer)(i + 1))); + assertFalse(q.contains(i + 1)); + } + assertTrue(q.isEmpty()); + } + + /** + * contains(x) reports true when elements added but not yet removed + */ + public void testContains() { + LinkedList q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.contains(new Integer(i))); + q.poll(); + assertFalse(q.contains(new Integer(i))); + } + } + + /** + * clear removes all elements + */ + public void testClear() { + LinkedList q = populatedQueue(SIZE); + q.clear(); + assertTrue(q.isEmpty()); + assertEquals(0, q.size()); + assertTrue(q.add(new Integer(1))); + assertFalse(q.isEmpty()); + q.clear(); + assertTrue(q.isEmpty()); + } + + /** + * containsAll(c) is true when c contains a subset of elements + */ + public void testContainsAll() { + LinkedList q = populatedQueue(SIZE); + LinkedList p = new LinkedList(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.containsAll(p)); + assertFalse(p.containsAll(q)); + assertTrue(p.add(new Integer(i))); + } + assertTrue(p.containsAll(q)); + } + + /** + * retainAll(c) retains only those elements of c and reports true if changed + */ + public void testRetainAll() { + LinkedList q = populatedQueue(SIZE); + LinkedList p = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + boolean changed = q.retainAll(p); + if (i == 0) + assertFalse(changed); + else + assertTrue(changed); + + assertTrue(q.containsAll(p)); + assertEquals(SIZE - i, q.size()); + p.remove(); + } + } + + /** + * removeAll(c) removes only those elements of c and reports true if changed + */ + public void testRemoveAll() { + for (int i = 1; i < SIZE; ++i) { + LinkedList q = populatedQueue(SIZE); + LinkedList p = populatedQueue(i); + assertTrue(q.removeAll(p)); + assertEquals(SIZE - i, q.size()); + for (int j = 0; j < i; ++j) { + Integer x = (Integer)(p.remove()); + assertFalse(q.contains(x)); + } + } + } + + /** + * toArray contains all elements in FIFO order + */ + public void testToArray() { + LinkedList q = populatedQueue(SIZE); + Object[] o = q.toArray(); + for (int i = 0; i < o.length; i++) + assertSame(o[i], q.poll()); + } + + /** + * toArray(a) contains all elements in FIFO order + */ + public void testToArray2() { + LinkedList q = populatedQueue(SIZE); + Integer[] ints = new Integer[SIZE]; + Integer[] array = q.toArray(ints); + assertSame(ints, array); + for (int i = 0; i < ints.length; i++) + assertSame(ints[i], q.poll()); + } + + /** + * toArray(null) throws NullPointerException + */ + public void testToArray_NullArg() { + LinkedList l = new LinkedList(); + l.add(new Object()); + try { + l.toArray(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * toArray(incompatible array type) throws ArrayStoreException + */ + public void testToArray1_BadArg() { + LinkedList l = new LinkedList(); + l.add(new Integer(5)); + try { + l.toArray(new String[10]); + shouldThrow(); + } catch (ArrayStoreException success) {} + } + + /** + * iterator iterates through all elements + */ + public void testIterator() { + LinkedList q = populatedQueue(SIZE); + Iterator it = q.iterator(); + int i; + for (i = 0; it.hasNext(); i++) + assertTrue(q.contains(it.next())); + assertEquals(i, SIZE); + assertIteratorExhausted(it); + } + + /** + * iterator of empty collection has no elements + */ + public void testEmptyIterator() { + assertIteratorExhausted(new LinkedList().iterator()); + } + + /** + * iterator ordering is FIFO + */ + public void testIteratorOrdering() { + final LinkedList q = new LinkedList(); + q.add(new Integer(1)); + q.add(new Integer(2)); + q.add(new Integer(3)); + int k = 0; + for (Iterator it = q.iterator(); it.hasNext();) { + assertEquals(++k, it.next()); + } + + assertEquals(3, k); + } + + /** + * iterator.remove removes current element + */ + public void testIteratorRemove() { + final LinkedList q = new LinkedList(); + q.add(new Integer(1)); + q.add(new Integer(2)); + q.add(new Integer(3)); + Iterator it = q.iterator(); + assertEquals(1, it.next()); + it.remove(); + it = q.iterator(); + assertEquals(2, it.next()); + assertEquals(3, it.next()); + assertFalse(it.hasNext()); + } + + /** + * Descending iterator iterates through all elements + */ + public void testDescendingIterator() { + LinkedList q = populatedQueue(SIZE); + int i = 0; + Iterator it = q.descendingIterator(); + while (it.hasNext()) { + assertTrue(q.contains(it.next())); + ++i; + } + assertEquals(i, SIZE); + assertFalse(it.hasNext()); + try { + it.next(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * Descending iterator ordering is reverse FIFO + */ + public void testDescendingIteratorOrdering() { + final LinkedList q = new LinkedList(); + q.add(new Integer(3)); + q.add(new Integer(2)); + q.add(new Integer(1)); + int k = 0; + for (Iterator it = q.descendingIterator(); it.hasNext();) { + assertEquals(++k, it.next()); + } + + assertEquals(3, k); + } + + /** + * descendingIterator.remove removes current element + */ + public void testDescendingIteratorRemove() { + final LinkedList q = new LinkedList(); + q.add(three); + q.add(two); + q.add(one); + Iterator it = q.descendingIterator(); + it.next(); + it.remove(); + it = q.descendingIterator(); + assertSame(it.next(), two); + assertSame(it.next(), three); + assertFalse(it.hasNext()); + } + + /** + * toString contains toStrings of elements + */ + public void testToString() { + LinkedList q = populatedQueue(SIZE); + String s = q.toString(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(s.contains(String.valueOf(i))); + } + } + + /** + * peek returns element inserted with addFirst + */ + public void testAddFirst() { + LinkedList q = populatedQueue(3); + q.addFirst(four); + assertSame(four, q.peek()); + } + + /** + * peekFirst returns element inserted with push + */ + public void testPush() { + LinkedList q = populatedQueue(3); + q.push(four); + assertSame(four, q.peekFirst()); + } + + /** + * pop removes next element, or throws NSEE if empty + */ + public void testPop() { + LinkedList q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.pop()); + } + try { + q.pop(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * OfferFirst succeeds + */ + public void testOfferFirst() { + LinkedList q = new LinkedList(); + assertTrue(q.offerFirst(new Integer(0))); + assertTrue(q.offerFirst(new Integer(1))); + } + + /** + * OfferLast succeeds + */ + public void testOfferLast() { + LinkedList q = new LinkedList(); + assertTrue(q.offerLast(new Integer(0))); + assertTrue(q.offerLast(new Integer(1))); + } + + /** + * pollLast succeeds unless empty + */ + public void testPollLast() { + LinkedList q = populatedQueue(SIZE); + for (int i = SIZE - 1; i >= 0; --i) { + assertEquals(i, q.pollLast()); + } + assertNull(q.pollLast()); + } + + /** + * peekFirst returns next element, or null if empty + */ + public void testPeekFirst() { + LinkedList q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.peekFirst()); + assertEquals(i, q.pollFirst()); + assertTrue(q.peekFirst() == null || + !q.peekFirst().equals(i)); + } + assertNull(q.peekFirst()); + } + + /** + * peekLast returns next element, or null if empty + */ + public void testPeekLast() { + LinkedList q = populatedQueue(SIZE); + for (int i = SIZE - 1; i >= 0; --i) { + assertEquals(i, q.peekLast()); + assertEquals(i, q.pollLast()); + assertTrue(q.peekLast() == null || + !q.peekLast().equals(i)); + } + assertNull(q.peekLast()); + } + + public void testFirstElement() { + LinkedList q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.getFirst()); + assertEquals(i, q.pollFirst()); + } + try { + q.getFirst(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * getLast returns next element, or throws NSEE if empty + */ + public void testLastElement() { + LinkedList q = populatedQueue(SIZE); + for (int i = SIZE - 1; i >= 0; --i) { + assertEquals(i, q.getLast()); + assertEquals(i, q.pollLast()); + } + try { + q.getLast(); + shouldThrow(); + } catch (NoSuchElementException success) {} + assertNull(q.peekLast()); + } + + /** + * removeFirstOccurrence(x) removes x and returns true if present + */ + public void testRemoveFirstOccurrence() { + LinkedList q = populatedQueue(SIZE); + for (int i = 1; i < SIZE; i += 2) { + assertTrue(q.removeFirstOccurrence(new Integer(i))); + } + for (int i = 0; i < SIZE; i += 2) { + assertTrue(q.removeFirstOccurrence(new Integer(i))); + assertFalse(q.removeFirstOccurrence(new Integer(i + 1))); + } + assertTrue(q.isEmpty()); + } + + /** + * removeLastOccurrence(x) removes x and returns true if present + */ + public void testRemoveLastOccurrence() { + LinkedList q = populatedQueue(SIZE); + for (int i = 1; i < SIZE; i += 2) { + assertTrue(q.removeLastOccurrence(new Integer(i))); + } + for (int i = 0; i < SIZE; i += 2) { + assertTrue(q.removeLastOccurrence(new Integer(i))); + assertFalse(q.removeLastOccurrence(new Integer(i + 1))); + } + assertTrue(q.isEmpty()); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/LinkedTransferQueueTest.java b/jdk/test/java/util/concurrent/tck/LinkedTransferQueueTest.java new file mode 100644 index 00000000000..dd57870a306 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/LinkedTransferQueueTest.java @@ -0,0 +1,1085 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include John Vint + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedTransferQueue; + +import junit.framework.Test; + +@SuppressWarnings({"unchecked", "rawtypes"}) +public class LinkedTransferQueueTest extends JSR166TestCase { + static class Implementation implements CollectionImplementation { + public Class klazz() { return LinkedTransferQueue.class; } + public Collection emptyCollection() { return new LinkedTransferQueue(); } + public Object makeElement(int i) { return i; } + public boolean isConcurrent() { return true; } + public boolean permitsNulls() { return false; } + } + + public static class Generic extends BlockingQueueTest { + protected BlockingQueue emptyCollection() { + return new LinkedTransferQueue(); + } + } + + public static void main(String[] args) { + main(suite(), args); + } + + public static Test suite() { + return newTestSuite(LinkedTransferQueueTest.class, + new Generic().testSuite(), + CollectionTest.testSuite(new Implementation())); + } + + /** + * Constructor builds new queue with size being zero and empty + * being true + */ + public void testConstructor1() { + assertEquals(0, new LinkedTransferQueue().size()); + assertTrue(new LinkedTransferQueue().isEmpty()); + } + + /** + * Initializing constructor with null collection throws + * NullPointerException + */ + public void testConstructor2() { + try { + new LinkedTransferQueue(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing from Collection of null elements throws + * NullPointerException + */ + public void testConstructor3() { + Collection elements = Arrays.asList(new Integer[SIZE]); + try { + new LinkedTransferQueue(elements); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing constructor with a collection containing some null elements + * throws NullPointerException + */ + public void testConstructor4() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = i; + Collection elements = Arrays.asList(ints); + try { + new LinkedTransferQueue(elements); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Queue contains all elements of the collection it is initialized by + */ + public void testConstructor5() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) { + ints[i] = i; + } + List intList = Arrays.asList(ints); + LinkedTransferQueue q + = new LinkedTransferQueue(intList); + assertEquals(q.size(), intList.size()); + assertEquals(q.toString(), intList.toString()); + assertTrue(Arrays.equals(q.toArray(), + intList.toArray())); + assertTrue(Arrays.equals(q.toArray(new Object[0]), + intList.toArray(new Object[0]))); + assertTrue(Arrays.equals(q.toArray(new Object[SIZE]), + intList.toArray(new Object[SIZE]))); + for (int i = 0; i < SIZE; ++i) { + assertEquals(ints[i], q.poll()); + } + } + + /** + * remainingCapacity() always returns Integer.MAX_VALUE + */ + public void testRemainingCapacity() { + BlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(Integer.MAX_VALUE, q.remainingCapacity()); + assertEquals(SIZE - i, q.size()); + assertEquals(i, q.remove()); + } + for (int i = 0; i < SIZE; ++i) { + assertEquals(Integer.MAX_VALUE, q.remainingCapacity()); + assertEquals(i, q.size()); + assertTrue(q.add(i)); + } + } + + /** + * addAll(this) throws IllegalArgumentException + */ + public void testAddAllSelf() { + LinkedTransferQueue q = populatedQueue(SIZE); + try { + q.addAll(q); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * addAll of a collection with any null elements throws + * NullPointerException after possibly adding some elements + */ + public void testAddAll3() { + LinkedTransferQueue q = new LinkedTransferQueue(); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = i; + try { + q.addAll(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Queue contains all elements, in traversal order, of successful addAll + */ + public void testAddAll5() { + Integer[] empty = new Integer[0]; + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) { + ints[i] = i; + } + LinkedTransferQueue q = new LinkedTransferQueue(); + assertFalse(q.addAll(Arrays.asList(empty))); + assertTrue(q.addAll(Arrays.asList(ints))); + for (int i = 0; i < SIZE; ++i) { + assertEquals(ints[i], q.poll()); + } + } + + /** + * all elements successfully put are contained + */ + public void testPut() { + LinkedTransferQueue q = new LinkedTransferQueue(); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.size()); + q.put(i); + assertTrue(q.contains(i)); + } + } + + /** + * take retrieves elements in FIFO order + */ + public void testTake() throws InterruptedException { + LinkedTransferQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, (int) q.take()); + } + } + + /** + * take removes existing elements until empty, then blocks interruptibly + */ + public void testBlockingTake() throws InterruptedException { + final BlockingQueue q = populatedQueue(SIZE); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.take()); + } + + Thread.currentThread().interrupt(); + try { + q.take(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + q.take(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * poll succeeds unless empty + */ + public void testPoll() throws InterruptedException { + LinkedTransferQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, (int) q.poll()); + } + assertNull(q.poll()); + checkEmpty(q); + } + + /** + * timed poll with zero timeout succeeds when non-empty, else times out + */ + public void testTimedPoll0() throws InterruptedException { + LinkedTransferQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, (int) q.poll(0, MILLISECONDS)); + } + assertNull(q.poll(0, MILLISECONDS)); + checkEmpty(q); + } + + /** + * timed poll with nonzero timeout succeeds when non-empty, else times out + */ + public void testTimedPoll() throws InterruptedException { + LinkedTransferQueue q = populatedQueue(SIZE); + long startTime = System.nanoTime(); + for (int i = 0; i < SIZE; ++i) + assertEquals(i, (int) q.poll(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + + startTime = System.nanoTime(); + assertNull(q.poll(timeoutMillis(), MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + checkEmpty(q); + } + + /** + * Interrupted timed poll throws InterruptedException instead of + * returning timeout status + */ + public void testInterruptedTimedPoll() throws InterruptedException { + final BlockingQueue q = populatedQueue(SIZE); + final CountDownLatch aboutToWait = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + for (int i = 0; i < SIZE; ++i) + assertEquals(i, (int) q.poll(LONG_DELAY_MS, MILLISECONDS)); + aboutToWait.countDown(); + try { + q.poll(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + }}); + + aboutToWait.await(); + waitForThreadToEnterWaitState(t); + t.interrupt(); + awaitTermination(t); + checkEmpty(q); + } + + /** + * timed poll after thread interrupted throws InterruptedException + * instead of returning timeout status + */ + public void testTimedPollAfterInterrupt() throws InterruptedException { + final BlockingQueue q = populatedQueue(SIZE); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + Thread.currentThread().interrupt(); + for (int i = 0; i < SIZE; ++i) + assertEquals(i, (int) q.poll(LONG_DELAY_MS, MILLISECONDS)); + try { + q.poll(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + }}); + + awaitTermination(t); + checkEmpty(q); + } + + /** + * peek returns next element, or null if empty + */ + public void testPeek() throws InterruptedException { + LinkedTransferQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, (int) q.peek()); + assertEquals(i, (int) q.poll()); + assertTrue(q.peek() == null || + i != (int) q.peek()); + } + assertNull(q.peek()); + checkEmpty(q); + } + + /** + * element returns next element, or throws NoSuchElementException if empty + */ + public void testElement() throws InterruptedException { + LinkedTransferQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, (int) q.element()); + assertEquals(i, (int) q.poll()); + } + try { + q.element(); + shouldThrow(); + } catch (NoSuchElementException success) {} + checkEmpty(q); + } + + /** + * remove removes next element, or throws NoSuchElementException if empty + */ + public void testRemove() throws InterruptedException { + LinkedTransferQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, (int) q.remove()); + } + try { + q.remove(); + shouldThrow(); + } catch (NoSuchElementException success) {} + checkEmpty(q); + } + + /** + * An add following remove(x) succeeds + */ + public void testRemoveElementAndAdd() throws InterruptedException { + LinkedTransferQueue q = new LinkedTransferQueue(); + assertTrue(q.add(one)); + assertTrue(q.add(two)); + assertTrue(q.remove(one)); + assertTrue(q.remove(two)); + assertTrue(q.add(three)); + assertSame(q.take(), three); + } + + /** + * contains(x) reports true when elements added but not yet removed + */ + public void testContains() { + LinkedTransferQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.contains(i)); + assertEquals(i, (int) q.poll()); + assertFalse(q.contains(i)); + } + } + + /** + * clear removes all elements + */ + public void testClear() throws InterruptedException { + LinkedTransferQueue q = populatedQueue(SIZE); + q.clear(); + checkEmpty(q); + assertEquals(Integer.MAX_VALUE, q.remainingCapacity()); + q.add(one); + assertFalse(q.isEmpty()); + assertEquals(1, q.size()); + assertTrue(q.contains(one)); + q.clear(); + checkEmpty(q); + } + + /** + * containsAll(c) is true when c contains a subset of elements + */ + public void testContainsAll() { + LinkedTransferQueue q = populatedQueue(SIZE); + LinkedTransferQueue p = new LinkedTransferQueue(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.containsAll(p)); + assertFalse(p.containsAll(q)); + p.add(i); + } + assertTrue(p.containsAll(q)); + } + + /** + * retainAll(c) retains only those elements of c and reports true + * if changed + */ + public void testRetainAll() { + LinkedTransferQueue q = populatedQueue(SIZE); + LinkedTransferQueue p = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + boolean changed = q.retainAll(p); + if (i == 0) { + assertFalse(changed); + } else { + assertTrue(changed); + } + assertTrue(q.containsAll(p)); + assertEquals(SIZE - i, q.size()); + p.remove(); + } + } + + /** + * removeAll(c) removes only those elements of c and reports true + * if changed + */ + public void testRemoveAll() { + for (int i = 1; i < SIZE; ++i) { + LinkedTransferQueue q = populatedQueue(SIZE); + LinkedTransferQueue p = populatedQueue(i); + assertTrue(q.removeAll(p)); + assertEquals(SIZE - i, q.size()); + for (int j = 0; j < i; ++j) { + assertFalse(q.contains(p.remove())); + } + } + } + + /** + * toArray() contains all elements in FIFO order + */ + public void testToArray() { + LinkedTransferQueue q = populatedQueue(SIZE); + Object[] o = q.toArray(); + for (int i = 0; i < o.length; i++) { + assertSame(o[i], q.poll()); + } + } + + /** + * toArray(a) contains all elements in FIFO order + */ + public void testToArray2() { + LinkedTransferQueue q = populatedQueue(SIZE); + Integer[] ints = new Integer[SIZE]; + Integer[] array = q.toArray(ints); + assertSame(ints, array); + for (int i = 0; i < ints.length; i++) { + assertSame(ints[i], q.poll()); + } + } + + /** + * toArray(incompatible array type) throws ArrayStoreException + */ + public void testToArray1_BadArg() { + LinkedTransferQueue q = populatedQueue(SIZE); + try { + q.toArray(new String[10]); + shouldThrow(); + } catch (ArrayStoreException success) {} + } + + /** + * iterator iterates through all elements + */ + public void testIterator() throws InterruptedException { + LinkedTransferQueue q = populatedQueue(SIZE); + Iterator it = q.iterator(); + int i; + for (i = 0; it.hasNext(); i++) + assertTrue(q.contains(it.next())); + assertEquals(i, SIZE); + assertIteratorExhausted(it); + + it = q.iterator(); + for (i = 0; it.hasNext(); i++) + assertEquals(it.next(), q.take()); + assertEquals(i, SIZE); + assertIteratorExhausted(it); + } + + /** + * iterator of empty collection has no elements + */ + public void testEmptyIterator() { + assertIteratorExhausted(new LinkedTransferQueue().iterator()); + } + + /** + * iterator.remove() removes current element + */ + public void testIteratorRemove() { + final LinkedTransferQueue q = new LinkedTransferQueue(); + q.add(two); + q.add(one); + q.add(three); + + Iterator it = q.iterator(); + it.next(); + it.remove(); + + it = q.iterator(); + assertSame(it.next(), one); + assertSame(it.next(), three); + assertFalse(it.hasNext()); + } + + /** + * iterator ordering is FIFO + */ + public void testIteratorOrdering() { + final LinkedTransferQueue q + = new LinkedTransferQueue(); + assertEquals(Integer.MAX_VALUE, q.remainingCapacity()); + q.add(one); + q.add(two); + q.add(three); + assertEquals(Integer.MAX_VALUE, q.remainingCapacity()); + int k = 0; + for (Integer n : q) { + assertEquals(++k, (int) n); + } + assertEquals(3, k); + } + + /** + * Modifications do not cause iterators to fail + */ + public void testWeaklyConsistentIteration() { + final LinkedTransferQueue q = new LinkedTransferQueue(); + q.add(one); + q.add(two); + q.add(three); + for (Iterator it = q.iterator(); it.hasNext();) { + q.remove(); + it.next(); + } + assertEquals(0, q.size()); + } + + /** + * toString contains toStrings of elements + */ + public void testToString() { + LinkedTransferQueue q = populatedQueue(SIZE); + String s = q.toString(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(s.contains(String.valueOf(i))); + } + } + + /** + * offer transfers elements across Executor tasks + */ + public void testOfferInExecutor() { + final LinkedTransferQueue q = new LinkedTransferQueue(); + final CheckedBarrier threadsStarted = new CheckedBarrier(2); + final ExecutorService executor = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(executor)) { + + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + long startTime = System.nanoTime(); + assertTrue(q.offer(one, LONG_DELAY_MS, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + }}); + + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + assertSame(one, q.take()); + checkEmpty(q); + }}); + } + } + + /** + * timed poll retrieves elements across Executor threads + */ + public void testPollInExecutor() { + final LinkedTransferQueue q = new LinkedTransferQueue(); + final CheckedBarrier threadsStarted = new CheckedBarrier(2); + final ExecutorService executor = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(executor)) { + + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertNull(q.poll()); + threadsStarted.await(); + long startTime = System.nanoTime(); + assertSame(one, q.poll(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + checkEmpty(q); + }}); + + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + q.put(one); + }}); + } + } + + /** + * A deserialized serialized queue has same elements in same order + */ + public void testSerialization() throws Exception { + Queue x = populatedQueue(SIZE); + Queue y = serialClone(x); + + assertNotSame(y, x); + assertEquals(x.size(), y.size()); + assertEquals(x.toString(), y.toString()); + assertTrue(Arrays.equals(x.toArray(), y.toArray())); + while (!x.isEmpty()) { + assertFalse(y.isEmpty()); + assertEquals(x.remove(), y.remove()); + } + assertTrue(y.isEmpty()); + } + + /** + * drainTo(c) empties queue into another collection c + */ + public void testDrainTo() { + LinkedTransferQueue q = populatedQueue(SIZE); + ArrayList l = new ArrayList(); + q.drainTo(l); + assertEquals(0, q.size()); + assertEquals(SIZE, l.size()); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, l.get(i)); + } + q.add(zero); + q.add(one); + assertFalse(q.isEmpty()); + assertTrue(q.contains(zero)); + assertTrue(q.contains(one)); + l.clear(); + q.drainTo(l); + assertEquals(0, q.size()); + assertEquals(2, l.size()); + for (int i = 0; i < 2; ++i) { + assertEquals(i, l.get(i)); + } + } + + /** + * drainTo(c) empties full queue, unblocking a waiting put. + */ + public void testDrainToWithActivePut() throws InterruptedException { + final LinkedTransferQueue q = populatedQueue(SIZE); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + q.put(SIZE + 1); + }}); + ArrayList l = new ArrayList(); + q.drainTo(l); + assertTrue(l.size() >= SIZE); + for (int i = 0; i < SIZE; ++i) + assertEquals(i, l.get(i)); + awaitTermination(t); + assertTrue(q.size() + l.size() >= SIZE); + } + + /** + * drainTo(c, n) empties first min(n, size) elements of queue into c + */ + public void testDrainToN() { + LinkedTransferQueue q = new LinkedTransferQueue(); + for (int i = 0; i < SIZE + 2; ++i) { + for (int j = 0; j < SIZE; j++) { + assertTrue(q.offer(j)); + } + ArrayList l = new ArrayList(); + q.drainTo(l, i); + int k = (i < SIZE) ? i : SIZE; + assertEquals(k, l.size()); + assertEquals(SIZE - k, q.size()); + for (int j = 0; j < k; ++j) + assertEquals(j, l.get(j)); + do {} while (q.poll() != null); + } + } + + /** + * timed poll() or take() increments the waiting consumer count; + * offer(e) decrements the waiting consumer count + */ + public void testWaitingConsumer() throws InterruptedException { + final LinkedTransferQueue q = new LinkedTransferQueue(); + assertEquals(0, q.getWaitingConsumerCount()); + assertFalse(q.hasWaitingConsumer()); + final CountDownLatch threadStarted = new CountDownLatch(1); + + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + long startTime = System.nanoTime(); + assertSame(one, q.poll(LONG_DELAY_MS, MILLISECONDS)); + assertEquals(0, q.getWaitingConsumerCount()); + assertFalse(q.hasWaitingConsumer()); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + }}); + + threadStarted.await(); + waitForThreadToEnterWaitState(t); + assertEquals(1, q.getWaitingConsumerCount()); + assertTrue(q.hasWaitingConsumer()); + + assertTrue(q.offer(one)); + assertEquals(0, q.getWaitingConsumerCount()); + assertFalse(q.hasWaitingConsumer()); + + awaitTermination(t); + } + + /** + * transfer(null) throws NullPointerException + */ + public void testTransfer1() throws InterruptedException { + try { + LinkedTransferQueue q = new LinkedTransferQueue(); + q.transfer(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * transfer waits until a poll occurs. The transfered element + * is returned by this associated poll. + */ + public void testTransfer2() throws InterruptedException { + final LinkedTransferQueue q + = new LinkedTransferQueue(); + final CountDownLatch threadStarted = new CountDownLatch(1); + + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + q.transfer(five); + checkEmpty(q); + }}); + + threadStarted.await(); + waitForThreadToEnterWaitState(t); + assertEquals(1, q.size()); + assertSame(five, q.poll()); + checkEmpty(q); + awaitTermination(t); + } + + /** + * transfer waits until a poll occurs, and then transfers in fifo order + */ + public void testTransfer3() throws InterruptedException { + final LinkedTransferQueue q + = new LinkedTransferQueue(); + + Thread first = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + q.transfer(four); + assertTrue(!q.contains(four)); + assertEquals(1, q.size()); + }}); + + Thread interruptedThread = newStartedThread( + new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + while (q.isEmpty()) + Thread.yield(); + q.transfer(five); + }}); + + while (q.size() < 2) + Thread.yield(); + assertEquals(2, q.size()); + assertSame(four, q.poll()); + first.join(); + assertEquals(1, q.size()); + interruptedThread.interrupt(); + interruptedThread.join(); + checkEmpty(q); + } + + /** + * transfer waits until a poll occurs, at which point the polling + * thread returns the element + */ + public void testTransfer4() throws InterruptedException { + final LinkedTransferQueue q = new LinkedTransferQueue(); + + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + q.transfer(four); + assertFalse(q.contains(four)); + assertSame(three, q.poll()); + }}); + + while (q.isEmpty()) + Thread.yield(); + assertFalse(q.isEmpty()); + assertEquals(1, q.size()); + assertTrue(q.offer(three)); + assertSame(four, q.poll()); + awaitTermination(t); + } + + /** + * transfer waits until a take occurs. The transfered element + * is returned by this associated take. + */ + public void testTransfer5() throws InterruptedException { + final LinkedTransferQueue q + = new LinkedTransferQueue(); + + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + q.transfer(four); + checkEmpty(q); + }}); + + while (q.isEmpty()) + Thread.yield(); + assertFalse(q.isEmpty()); + assertEquals(1, q.size()); + assertSame(four, q.take()); + checkEmpty(q); + awaitTermination(t); + } + + /** + * tryTransfer(null) throws NullPointerException + */ + public void testTryTransfer1() { + final LinkedTransferQueue q = new LinkedTransferQueue(); + try { + q.tryTransfer(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * tryTransfer returns false and does not enqueue if there are no + * consumers waiting to poll or take. + */ + public void testTryTransfer2() throws InterruptedException { + final LinkedTransferQueue q = new LinkedTransferQueue(); + assertFalse(q.tryTransfer(new Object())); + assertFalse(q.hasWaitingConsumer()); + checkEmpty(q); + } + + /** + * If there is a consumer waiting in timed poll, tryTransfer + * returns true while successfully transfering object. + */ + public void testTryTransfer3() throws InterruptedException { + final Object hotPotato = new Object(); + final LinkedTransferQueue q = new LinkedTransferQueue(); + + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + while (! q.hasWaitingConsumer()) + Thread.yield(); + assertTrue(q.hasWaitingConsumer()); + checkEmpty(q); + assertTrue(q.tryTransfer(hotPotato)); + }}); + + long startTime = System.nanoTime(); + assertSame(hotPotato, q.poll(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + checkEmpty(q); + awaitTermination(t); + } + + /** + * If there is a consumer waiting in take, tryTransfer returns + * true while successfully transfering object. + */ + public void testTryTransfer4() throws InterruptedException { + final Object hotPotato = new Object(); + final LinkedTransferQueue q = new LinkedTransferQueue(); + + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + while (! q.hasWaitingConsumer()) + Thread.yield(); + assertTrue(q.hasWaitingConsumer()); + checkEmpty(q); + assertTrue(q.tryTransfer(hotPotato)); + }}); + + assertSame(q.take(), hotPotato); + checkEmpty(q); + awaitTermination(t); + } + + /** + * tryTransfer blocks interruptibly if no takers + */ + public void testTryTransfer5() throws InterruptedException { + final LinkedTransferQueue q = new LinkedTransferQueue(); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + assertTrue(q.isEmpty()); + + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + Thread.currentThread().interrupt(); + try { + q.tryTransfer(new Object(), LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + q.tryTransfer(new Object(), LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + checkEmpty(q); + } + + /** + * tryTransfer gives up after the timeout and returns false + */ + public void testTryTransfer6() throws InterruptedException { + final LinkedTransferQueue q = new LinkedTransferQueue(); + + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + assertFalse(q.tryTransfer(new Object(), + timeoutMillis(), MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + checkEmpty(q); + }}); + + awaitTermination(t); + checkEmpty(q); + } + + /** + * tryTransfer waits for any elements previously in to be removed + * before transfering to a poll or take + */ + public void testTryTransfer7() throws InterruptedException { + final LinkedTransferQueue q = new LinkedTransferQueue(); + assertTrue(q.offer(four)); + + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + assertTrue(q.tryTransfer(five, LONG_DELAY_MS, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + checkEmpty(q); + }}); + + while (q.size() != 2) + Thread.yield(); + assertEquals(2, q.size()); + assertSame(four, q.poll()); + assertSame(five, q.poll()); + checkEmpty(q); + awaitTermination(t); + } + + /** + * tryTransfer attempts to enqueue into the queue and fails + * returning false not enqueueing and the successive poll is null + */ + public void testTryTransfer8() throws InterruptedException { + final LinkedTransferQueue q = new LinkedTransferQueue(); + assertTrue(q.offer(four)); + assertEquals(1, q.size()); + long startTime = System.nanoTime(); + assertFalse(q.tryTransfer(five, timeoutMillis(), MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + assertEquals(1, q.size()); + assertSame(four, q.poll()); + assertNull(q.poll()); + checkEmpty(q); + } + + private LinkedTransferQueue populatedQueue(int n) { + LinkedTransferQueue q = new LinkedTransferQueue(); + checkEmpty(q); + for (int i = 0; i < n; i++) { + assertEquals(i, q.size()); + assertTrue(q.offer(i)); + assertEquals(Integer.MAX_VALUE, q.remainingCapacity()); + } + assertFalse(q.isEmpty()); + return q; + } + + /** + * remove(null), contains(null) always return false + */ + public void testNeverContainsNull() { + Collection[] qs = { + new LinkedTransferQueue(), + populatedQueue(2), + }; + + for (Collection q : qs) { + assertFalse(q.contains(null)); + assertFalse(q.remove(null)); + } + } +} diff --git a/jdk/test/java/util/concurrent/tck/LockSupportTest.java b/jdk/test/java/util/concurrent/tck/LockSupportTest.java new file mode 100644 index 00000000000..02175ad1051 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/LockSupportTest.java @@ -0,0 +1,403 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea and Martin Buchholz with assistance from + * members of JCP JSR-166 Expert Group and released to the public + * domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.LockSupport; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class LockSupportTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + + public static Test suite() { + return new TestSuite(LockSupportTest.class); + } + + static { + // Reduce the risk of rare disastrous classloading in first call to + // LockSupport.park: https://bugs.openjdk.java.net/browse/JDK-8074773 + Class ensureLoaded = LockSupport.class; + } + + /** + * Returns the blocker object used by tests in this file. + * Any old object will do; we'll return a convenient one. + */ + static Object theBlocker() { + return LockSupportTest.class; + } + + enum ParkMethod { + park() { + void park() { + LockSupport.park(); + } + void park(long millis) { + throw new UnsupportedOperationException(); + } + }, + parkUntil() { + void park(long millis) { + LockSupport.parkUntil(deadline(millis)); + } + }, + parkNanos() { + void park(long millis) { + LockSupport.parkNanos(MILLISECONDS.toNanos(millis)); + } + }, + parkBlocker() { + void park() { + LockSupport.park(theBlocker()); + } + void park(long millis) { + throw new UnsupportedOperationException(); + } + }, + parkUntilBlocker() { + void park(long millis) { + LockSupport.parkUntil(theBlocker(), deadline(millis)); + } + }, + parkNanosBlocker() { + void park(long millis) { + LockSupport.parkNanos(theBlocker(), + MILLISECONDS.toNanos(millis)); + } + }; + + void park() { park(2 * LONG_DELAY_MS); } + abstract void park(long millis); + + /** Returns a deadline to use with parkUntil. */ + long deadline(long millis) { + // beware of rounding + return System.currentTimeMillis() + millis + 1; + } + } + + /** + * park is released by subsequent unpark + */ + public void testParkBeforeUnpark_park() { + testParkBeforeUnpark(ParkMethod.park); + } + public void testParkBeforeUnpark_parkNanos() { + testParkBeforeUnpark(ParkMethod.parkNanos); + } + public void testParkBeforeUnpark_parkUntil() { + testParkBeforeUnpark(ParkMethod.parkUntil); + } + public void testParkBeforeUnpark_parkBlocker() { + testParkBeforeUnpark(ParkMethod.parkBlocker); + } + public void testParkBeforeUnpark_parkNanosBlocker() { + testParkBeforeUnpark(ParkMethod.parkNanosBlocker); + } + public void testParkBeforeUnpark_parkUntilBlocker() { + testParkBeforeUnpark(ParkMethod.parkUntilBlocker); + } + public void testParkBeforeUnpark(final ParkMethod parkMethod) { + final CountDownLatch pleaseUnpark = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + pleaseUnpark.countDown(); + parkMethod.park(); + }}); + + await(pleaseUnpark); + LockSupport.unpark(t); + awaitTermination(t); + } + + /** + * park is released by preceding unpark + */ + public void testParkAfterUnpark_park() { + testParkAfterUnpark(ParkMethod.park); + } + public void testParkAfterUnpark_parkNanos() { + testParkAfterUnpark(ParkMethod.parkNanos); + } + public void testParkAfterUnpark_parkUntil() { + testParkAfterUnpark(ParkMethod.parkUntil); + } + public void testParkAfterUnpark_parkBlocker() { + testParkAfterUnpark(ParkMethod.parkBlocker); + } + public void testParkAfterUnpark_parkNanosBlocker() { + testParkAfterUnpark(ParkMethod.parkNanosBlocker); + } + public void testParkAfterUnpark_parkUntilBlocker() { + testParkAfterUnpark(ParkMethod.parkUntilBlocker); + } + public void testParkAfterUnpark(final ParkMethod parkMethod) { + final CountDownLatch pleaseUnpark = new CountDownLatch(1); + final AtomicBoolean pleasePark = new AtomicBoolean(false); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + pleaseUnpark.countDown(); + while (!pleasePark.get()) + Thread.yield(); + parkMethod.park(); + }}); + + await(pleaseUnpark); + LockSupport.unpark(t); + pleasePark.set(true); + awaitTermination(t); + } + + /** + * park is released by subsequent interrupt + */ + public void testParkBeforeInterrupt_park() { + testParkBeforeInterrupt(ParkMethod.park); + } + public void testParkBeforeInterrupt_parkNanos() { + testParkBeforeInterrupt(ParkMethod.parkNanos); + } + public void testParkBeforeInterrupt_parkUntil() { + testParkBeforeInterrupt(ParkMethod.parkUntil); + } + public void testParkBeforeInterrupt_parkBlocker() { + testParkBeforeInterrupt(ParkMethod.parkBlocker); + } + public void testParkBeforeInterrupt_parkNanosBlocker() { + testParkBeforeInterrupt(ParkMethod.parkNanosBlocker); + } + public void testParkBeforeInterrupt_parkUntilBlocker() { + testParkBeforeInterrupt(ParkMethod.parkUntilBlocker); + } + public void testParkBeforeInterrupt(final ParkMethod parkMethod) { + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + pleaseInterrupt.countDown(); + do { + parkMethod.park(); + // park may return spuriously + } while (! Thread.currentThread().isInterrupted()); + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * park is released by preceding interrupt + */ + public void testParkAfterInterrupt_park() { + testParkAfterInterrupt(ParkMethod.park); + } + public void testParkAfterInterrupt_parkNanos() { + testParkAfterInterrupt(ParkMethod.parkNanos); + } + public void testParkAfterInterrupt_parkUntil() { + testParkAfterInterrupt(ParkMethod.parkUntil); + } + public void testParkAfterInterrupt_parkBlocker() { + testParkAfterInterrupt(ParkMethod.parkBlocker); + } + public void testParkAfterInterrupt_parkNanosBlocker() { + testParkAfterInterrupt(ParkMethod.parkNanosBlocker); + } + public void testParkAfterInterrupt_parkUntilBlocker() { + testParkAfterInterrupt(ParkMethod.parkUntilBlocker); + } + public void testParkAfterInterrupt(final ParkMethod parkMethod) { + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + final AtomicBoolean pleasePark = new AtomicBoolean(false); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws Exception { + pleaseInterrupt.countDown(); + while (!pleasePark.get()) + Thread.yield(); + assertTrue(Thread.currentThread().isInterrupted()); + parkMethod.park(); + assertTrue(Thread.currentThread().isInterrupted()); + }}); + + await(pleaseInterrupt); + t.interrupt(); + pleasePark.set(true); + awaitTermination(t); + } + + /** + * timed park times out if not unparked + */ + public void testParkTimesOut_parkNanos() { + testParkTimesOut(ParkMethod.parkNanos); + } + public void testParkTimesOut_parkUntil() { + testParkTimesOut(ParkMethod.parkUntil); + } + public void testParkTimesOut_parkNanosBlocker() { + testParkTimesOut(ParkMethod.parkNanosBlocker); + } + public void testParkTimesOut_parkUntilBlocker() { + testParkTimesOut(ParkMethod.parkUntilBlocker); + } + public void testParkTimesOut(final ParkMethod parkMethod) { + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + for (;;) { + long startTime = System.nanoTime(); + parkMethod.park(timeoutMillis()); + // park may return spuriously + if (millisElapsedSince(startTime) >= timeoutMillis()) + return; + } + }}); + + awaitTermination(t); + } + + /** + * getBlocker(null) throws NullPointerException + */ + public void testGetBlockerNull() { + try { + LockSupport.getBlocker(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * getBlocker returns the blocker object passed to park + */ + public void testGetBlocker_parkBlocker() { + testGetBlocker(ParkMethod.parkBlocker); + } + public void testGetBlocker_parkNanosBlocker() { + testGetBlocker(ParkMethod.parkNanosBlocker); + } + public void testGetBlocker_parkUntilBlocker() { + testGetBlocker(ParkMethod.parkUntilBlocker); + } + public void testGetBlocker(final ParkMethod parkMethod) { + final CountDownLatch started = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + Thread t = Thread.currentThread(); + started.countDown(); + do { + assertNull(LockSupport.getBlocker(t)); + parkMethod.park(); + assertNull(LockSupport.getBlocker(t)); + // park may return spuriously + } while (! Thread.currentThread().isInterrupted()); + }}); + + long startTime = System.nanoTime(); + await(started); + for (;;) { + Object x = LockSupport.getBlocker(t); + if (x == theBlocker()) { // success + t.interrupt(); + awaitTermination(t); + assertNull(LockSupport.getBlocker(t)); + return; + } else { + assertNull(x); // ok + if (millisElapsedSince(startTime) > LONG_DELAY_MS) + fail("timed out"); + Thread.yield(); + } + } + } + + /** + * timed park(0) returns immediately. + * + * Requires hotspot fix for: + * 6763959 java.util.concurrent.locks.LockSupport.parkUntil(0) blocks forever + * which is in jdk7-b118 and 6u25. + */ + public void testPark0_parkNanos() { + testPark0(ParkMethod.parkNanos); + } + public void testPark0_parkUntil() { + testPark0(ParkMethod.parkUntil); + } + public void testPark0_parkNanosBlocker() { + testPark0(ParkMethod.parkNanosBlocker); + } + public void testPark0_parkUntilBlocker() { + testPark0(ParkMethod.parkUntilBlocker); + } + public void testPark0(final ParkMethod parkMethod) { + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + parkMethod.park(0L); + }}); + + awaitTermination(t); + } + + /** + * timed park(Long.MIN_VALUE) returns immediately. + */ + public void testParkNeg_parkNanos() { + testParkNeg(ParkMethod.parkNanos); + } + public void testParkNeg_parkUntil() { + testParkNeg(ParkMethod.parkUntil); + } + public void testParkNeg_parkNanosBlocker() { + testParkNeg(ParkMethod.parkNanosBlocker); + } + public void testParkNeg_parkUntilBlocker() { + testParkNeg(ParkMethod.parkUntilBlocker); + } + public void testParkNeg(final ParkMethod parkMethod) { + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + parkMethod.park(Long.MIN_VALUE); + }}); + + awaitTermination(t); + } +} diff --git a/jdk/test/java/util/concurrent/tck/LongAccumulatorTest.java b/jdk/test/java/util/concurrent/tck/LongAccumulatorTest.java new file mode 100644 index 00000000000..6acfd27efc7 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/LongAccumulatorTest.java @@ -0,0 +1,183 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Phaser; +import java.util.concurrent.atomic.LongAccumulator; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class LongAccumulatorTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(LongAccumulatorTest.class); + } + + /** + * default constructed initializes to zero + */ + public void testConstructor() { + LongAccumulator ai = new LongAccumulator(Long::max, 0L); + assertEquals(0, ai.get()); + } + + /** + * accumulate accumulates given value to current, and get returns current value + */ + public void testAccumulateAndGet() { + LongAccumulator ai = new LongAccumulator(Long::max, 0L); + ai.accumulate(2); + assertEquals(2, ai.get()); + ai.accumulate(-4); + assertEquals(2, ai.get()); + ai.accumulate(4); + assertEquals(4, ai.get()); + } + + /** + * reset() causes subsequent get() to return zero + */ + public void testReset() { + LongAccumulator ai = new LongAccumulator(Long::max, 0L); + ai.accumulate(2); + assertEquals(2, ai.get()); + ai.reset(); + assertEquals(0, ai.get()); + } + + /** + * getThenReset() returns current value; subsequent get() returns zero + */ + public void testGetThenReset() { + LongAccumulator ai = new LongAccumulator(Long::max, 0L); + ai.accumulate(2); + assertEquals(2, ai.get()); + assertEquals(2, ai.getThenReset()); + assertEquals(0, ai.get()); + } + + /** + * toString returns current value. + */ + public void testToString() { + LongAccumulator ai = new LongAccumulator(Long::max, 0L); + assertEquals("0", ai.toString()); + ai.accumulate(1); + assertEquals(Long.toString(1), ai.toString()); + } + + /** + * intValue returns current value. + */ + public void testIntValue() { + LongAccumulator ai = new LongAccumulator(Long::max, 0L); + assertEquals(0, ai.intValue()); + ai.accumulate(1); + assertEquals(1, ai.intValue()); + } + + /** + * longValue returns current value. + */ + public void testLongValue() { + LongAccumulator ai = new LongAccumulator(Long::max, 0L); + assertEquals(0, ai.longValue()); + ai.accumulate(1); + assertEquals(1, ai.longValue()); + } + + /** + * floatValue returns current value. + */ + public void testFloatValue() { + LongAccumulator ai = new LongAccumulator(Long::max, 0L); + assertEquals(0.0f, ai.floatValue()); + ai.accumulate(1); + assertEquals(1.0f, ai.floatValue()); + } + + /** + * doubleValue returns current value. + */ + public void testDoubleValue() { + LongAccumulator ai = new LongAccumulator(Long::max, 0L); + assertEquals(0.0, ai.doubleValue()); + ai.accumulate(1); + assertEquals(1.0, ai.doubleValue()); + } + + /** + * accumulates by multiple threads produce correct result + */ + public void testAccumulateAndGetMT() { + final int incs = 1000000; + final int nthreads = 4; + final ExecutorService pool = Executors.newCachedThreadPool(); + LongAccumulator a = new LongAccumulator(Long::max, 0L); + Phaser phaser = new Phaser(nthreads + 1); + for (int i = 0; i < nthreads; ++i) + pool.execute(new AccTask(a, phaser, incs)); + phaser.arriveAndAwaitAdvance(); + phaser.arriveAndAwaitAdvance(); + long expected = incs - 1; + long result = a.get(); + assertEquals(expected, result); + pool.shutdown(); + } + + static final class AccTask implements Runnable { + final LongAccumulator acc; + final Phaser phaser; + final int incs; + volatile long result; + AccTask(LongAccumulator acc, Phaser phaser, int incs) { + this.acc = acc; + this.phaser = phaser; + this.incs = incs; + } + + public void run() { + phaser.arriveAndAwaitAdvance(); + LongAccumulator a = acc; + for (int i = 0; i < incs; ++i) + a.accumulate(i); + result = a.get(); + phaser.arrive(); + } + } + +} diff --git a/jdk/test/java/util/concurrent/tck/LongAdderTest.java b/jdk/test/java/util/concurrent/tck/LongAdderTest.java new file mode 100644 index 00000000000..4f0d5e07f75 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/LongAdderTest.java @@ -0,0 +1,220 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.LongAdder; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class LongAdderTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(LongAdderTest.class); + } + + /** + * default constructed initializes to zero + */ + public void testConstructor() { + LongAdder ai = new LongAdder(); + assertEquals(0, ai.sum()); + } + + /** + * add adds given value to current, and sum returns current value + */ + public void testAddAndSum() { + LongAdder ai = new LongAdder(); + ai.add(2); + assertEquals(2, ai.sum()); + ai.add(-4); + assertEquals(-2, ai.sum()); + } + + /** + * decrement decrements and sum returns current value + */ + public void testDecrementAndsum() { + LongAdder ai = new LongAdder(); + ai.decrement(); + assertEquals(-1, ai.sum()); + ai.decrement(); + assertEquals(-2, ai.sum()); + } + + /** + * incrementAndGet increments and returns current value + */ + public void testIncrementAndsum() { + LongAdder ai = new LongAdder(); + ai.increment(); + assertEquals(1, ai.sum()); + ai.increment(); + assertEquals(2, ai.sum()); + } + + /** + * reset() causes subsequent sum() to return zero + */ + public void testReset() { + LongAdder ai = new LongAdder(); + ai.add(2); + assertEquals(2, ai.sum()); + ai.reset(); + assertEquals(0, ai.sum()); + } + + /** + * sumThenReset() returns sum; subsequent sum() returns zero + */ + public void testSumThenReset() { + LongAdder ai = new LongAdder(); + ai.add(2); + assertEquals(2, ai.sum()); + assertEquals(2, ai.sumThenReset()); + assertEquals(0, ai.sum()); + } + + /** + * a deserialized serialized adder holds same value + */ + public void testSerialization() throws Exception { + LongAdder x = new LongAdder(); + LongAdder y = serialClone(x); + assertNotSame(x, y); + x.add(-22); + LongAdder z = serialClone(x); + assertNotSame(y, z); + assertEquals(-22, x.sum()); + assertEquals(0, y.sum()); + assertEquals(-22, z.sum()); + } + + /** + * toString returns current value. + */ + public void testToString() { + LongAdder ai = new LongAdder(); + assertEquals("0", ai.toString()); + ai.increment(); + assertEquals(Long.toString(1), ai.toString()); + } + + /** + * intValue returns current value. + */ + public void testIntValue() { + LongAdder ai = new LongAdder(); + assertEquals(0, ai.intValue()); + ai.increment(); + assertEquals(1, ai.intValue()); + } + + /** + * longValue returns current value. + */ + public void testLongValue() { + LongAdder ai = new LongAdder(); + assertEquals(0, ai.longValue()); + ai.increment(); + assertEquals(1, ai.longValue()); + } + + /** + * floatValue returns current value. + */ + public void testFloatValue() { + LongAdder ai = new LongAdder(); + assertEquals(0.0f, ai.floatValue()); + ai.increment(); + assertEquals(1.0f, ai.floatValue()); + } + + /** + * doubleValue returns current value. + */ + public void testDoubleValue() { + LongAdder ai = new LongAdder(); + assertEquals(0.0, ai.doubleValue()); + ai.increment(); + assertEquals(1.0, ai.doubleValue()); + } + + /** + * adds by multiple threads produce correct sum + */ + public void testAddAndSumMT() throws Throwable { + final int incs = 1000000; + final int nthreads = 4; + final ExecutorService pool = Executors.newCachedThreadPool(); + LongAdder a = new LongAdder(); + CyclicBarrier barrier = new CyclicBarrier(nthreads + 1); + for (int i = 0; i < nthreads; ++i) + pool.execute(new AdderTask(a, barrier, incs)); + barrier.await(); + barrier.await(); + long total = (long)nthreads * incs; + long sum = a.sum(); + assertEquals(sum, total); + pool.shutdown(); + } + + static final class AdderTask implements Runnable { + final LongAdder adder; + final CyclicBarrier barrier; + final int incs; + volatile long result; + AdderTask(LongAdder adder, CyclicBarrier barrier, int incs) { + this.adder = adder; + this.barrier = barrier; + this.incs = incs; + } + + public void run() { + try { + barrier.await(); + LongAdder a = adder; + for (int i = 0; i < incs; ++i) + a.add(1L); + result = a.sum(); + barrier.await(); + } catch (Throwable t) { throw new Error(t); } + } + } + +} diff --git a/jdk/test/java/util/concurrent/tck/PhaserTest.java b/jdk/test/java/util/concurrent/tck/PhaserTest.java new file mode 100644 index 00000000000..69b6c808ff8 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/PhaserTest.java @@ -0,0 +1,820 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include John Vint + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Phaser; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class PhaserTest extends JSR166TestCase { + + public static void main(String[] args) { + main(suite(), args); + } + + public static Test suite() { + return new TestSuite(PhaserTest.class); + } + + private static final int maxParties = 65535; + + /** Checks state of unterminated phaser. */ + protected void assertState(Phaser phaser, + int phase, int parties, int unarrived) { + assertEquals(phase, phaser.getPhase()); + assertEquals(parties, phaser.getRegisteredParties()); + assertEquals(unarrived, phaser.getUnarrivedParties()); + assertEquals(parties - unarrived, phaser.getArrivedParties()); + assertFalse(phaser.isTerminated()); + } + + /** Checks state of terminated phaser. */ + protected void assertTerminated(Phaser phaser, int maxPhase, int parties) { + assertTrue(phaser.isTerminated()); + int expectedPhase = maxPhase + Integer.MIN_VALUE; + assertEquals(expectedPhase, phaser.getPhase()); + assertEquals(parties, phaser.getRegisteredParties()); + assertEquals(expectedPhase, phaser.register()); + assertEquals(expectedPhase, phaser.arrive()); + assertEquals(expectedPhase, phaser.arriveAndDeregister()); + } + + protected void assertTerminated(Phaser phaser, int maxPhase) { + assertTerminated(phaser, maxPhase, 0); + } + + /** + * Empty constructor builds a new Phaser with no parent, no registered + * parties and initial phase number of 0 + */ + public void testConstructorDefaultValues() { + Phaser phaser = new Phaser(); + assertNull(phaser.getParent()); + assertEquals(0, phaser.getRegisteredParties()); + assertEquals(0, phaser.getArrivedParties()); + assertEquals(0, phaser.getUnarrivedParties()); + assertEquals(0, phaser.getPhase()); + } + + /** + * Constructing with a negative number of parties throws + * IllegalArgumentException + */ + public void testConstructorNegativeParties() { + try { + new Phaser(-1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructing with a negative number of parties throws + * IllegalArgumentException + */ + public void testConstructorNegativeParties2() { + try { + new Phaser(new Phaser(), -1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructing with a number of parties > 65535 throws + * IllegalArgumentException + */ + public void testConstructorPartiesExceedsLimit() { + new Phaser(maxParties); + try { + new Phaser(maxParties + 1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + + new Phaser(new Phaser(), maxParties); + try { + new Phaser(new Phaser(), maxParties + 1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * The parent provided to the constructor should be returned from + * a later call to getParent + */ + public void testConstructor3() { + Phaser parent = new Phaser(); + assertSame(parent, new Phaser(parent).getParent()); + assertNull(new Phaser(null).getParent()); + } + + /** + * The parent being input into the parameter should equal the original + * parent when being returned + */ + public void testConstructor5() { + Phaser parent = new Phaser(); + assertSame(parent, new Phaser(parent, 0).getParent()); + assertNull(new Phaser(null, 0).getParent()); + } + + /** + * register() will increment the number of unarrived parties by + * one and not affect its arrived parties + */ + public void testRegister1() { + Phaser phaser = new Phaser(); + assertState(phaser, 0, 0, 0); + assertEquals(0, phaser.register()); + assertState(phaser, 0, 1, 1); + } + + /** + * Registering more than 65536 parties causes IllegalStateException + */ + public void testRegister2() { + Phaser phaser = new Phaser(0); + assertState(phaser, 0, 0, 0); + assertEquals(0, phaser.bulkRegister(maxParties - 10)); + assertState(phaser, 0, maxParties - 10, maxParties - 10); + for (int i = 0; i < 10; i++) { + assertState(phaser, 0, maxParties - 10 + i, maxParties - 10 + i); + assertEquals(0, phaser.register()); + } + assertState(phaser, 0, maxParties, maxParties); + try { + phaser.register(); + shouldThrow(); + } catch (IllegalStateException success) {} + + try { + phaser.bulkRegister(Integer.MAX_VALUE); + shouldThrow(); + } catch (IllegalStateException success) {} + + assertEquals(0, phaser.bulkRegister(0)); + assertState(phaser, 0, maxParties, maxParties); + } + + /** + * register() correctly returns the current barrier phase number + * when invoked + */ + public void testRegister3() { + Phaser phaser = new Phaser(); + assertEquals(0, phaser.register()); + assertEquals(0, phaser.arrive()); + assertEquals(1, phaser.register()); + assertState(phaser, 1, 2, 2); + } + + /** + * register causes the next arrive to not increment the phase + * rather retain the phase number + */ + public void testRegister4() { + Phaser phaser = new Phaser(1); + assertEquals(0, phaser.arrive()); + assertEquals(1, phaser.register()); + assertEquals(1, phaser.arrive()); + assertState(phaser, 1, 2, 1); + } + + /** + * register on a subphaser that is currently empty succeeds, even + * in the presence of another non-empty subphaser + */ + public void testRegisterEmptySubPhaser() { + Phaser root = new Phaser(); + Phaser child1 = new Phaser(root, 1); + Phaser child2 = new Phaser(root, 0); + assertEquals(0, child2.register()); + assertState(root, 0, 2, 2); + assertState(child1, 0, 1, 1); + assertState(child2, 0, 1, 1); + assertEquals(0, child2.arriveAndDeregister()); + assertState(root, 0, 1, 1); + assertState(child1, 0, 1, 1); + assertState(child2, 0, 0, 0); + assertEquals(0, child2.register()); + assertEquals(0, child2.arriveAndDeregister()); + assertState(root, 0, 1, 1); + assertState(child1, 0, 1, 1); + assertState(child2, 0, 0, 0); + assertEquals(0, child1.arriveAndDeregister()); + assertTerminated(root, 1); + assertTerminated(child1, 1); + assertTerminated(child2, 1); + } + + /** + * Invoking bulkRegister with a negative parameter throws an + * IllegalArgumentException + */ + public void testBulkRegister1() { + try { + new Phaser().bulkRegister(-1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * bulkRegister should correctly record the number of unarrived + * parties with the number of parties being registered + */ + public void testBulkRegister2() { + Phaser phaser = new Phaser(); + assertEquals(0, phaser.bulkRegister(0)); + assertState(phaser, 0, 0, 0); + assertEquals(0, phaser.bulkRegister(20)); + assertState(phaser, 0, 20, 20); + } + + /** + * Registering with a number of parties greater than or equal to 1<<16 + * throws IllegalStateException. + */ + public void testBulkRegister3() { + assertEquals(0, new Phaser().bulkRegister((1 << 16) - 1)); + + try { + new Phaser().bulkRegister(1 << 16); + shouldThrow(); + } catch (IllegalStateException success) {} + + try { + new Phaser(2).bulkRegister((1 << 16) - 2); + shouldThrow(); + } catch (IllegalStateException success) {} + } + + /** + * the phase number increments correctly when tripping the barrier + */ + public void testPhaseIncrement1() { + for (int size = 1; size < nine; size++) { + final Phaser phaser = new Phaser(size); + for (int index = 0; index <= (1 << size); index++) { + int phase = phaser.arrive(); + assertTrue(index % size == 0 ? (index / size) == phase : index - (phase * size) > 0); + } + } + } + + /** + * arrive() on a registered phaser increments phase. + */ + public void testArrive1() { + Phaser phaser = new Phaser(1); + assertState(phaser, 0, 1, 1); + assertEquals(0, phaser.arrive()); + assertState(phaser, 1, 1, 1); + } + + /** + * arriveAndDeregister does not wait for others to arrive at barrier + */ + public void testArriveAndDeregister() { + final Phaser phaser = new Phaser(1); + for (int i = 0; i < 10; i++) { + assertState(phaser, 0, 1, 1); + assertEquals(0, phaser.register()); + assertState(phaser, 0, 2, 2); + assertEquals(0, phaser.arriveAndDeregister()); + assertState(phaser, 0, 1, 1); + } + assertEquals(0, phaser.arriveAndDeregister()); + assertTerminated(phaser, 1); + } + + /** + * arriveAndDeregister does not wait for others to arrive at barrier + */ + public void testArrive2() { + final Phaser phaser = new Phaser(); + assertEquals(0, phaser.register()); + List threads = new ArrayList(); + for (int i = 0; i < 10; i++) { + assertEquals(0, phaser.register()); + threads.add(newStartedThread(new CheckedRunnable() { + public void realRun() { + assertEquals(0, phaser.arriveAndDeregister()); + }})); + } + + for (Thread thread : threads) + awaitTermination(thread); + assertState(phaser, 0, 1, 1); + assertEquals(0, phaser.arrive()); + assertState(phaser, 1, 1, 1); + } + + /** + * arrive() returns a negative number if the Phaser is terminated + */ + public void testArrive3() { + Phaser phaser = new Phaser(1); + phaser.forceTermination(); + assertTerminated(phaser, 0, 1); + assertEquals(0, phaser.getPhase() + Integer.MIN_VALUE); + assertTrue(phaser.arrive() < 0); + assertTrue(phaser.register() < 0); + assertTrue(phaser.arriveAndDeregister() < 0); + assertTrue(phaser.awaitAdvance(1) < 0); + assertTrue(phaser.getPhase() < 0); + } + + /** + * arriveAndDeregister() throws IllegalStateException if number of + * registered or unarrived parties would become negative + */ + public void testArriveAndDeregister1() { + Phaser phaser = new Phaser(); + try { + phaser.arriveAndDeregister(); + shouldThrow(); + } catch (IllegalStateException success) {} + } + + /** + * arriveAndDeregister reduces the number of arrived parties + */ + public void testArriveAndDeregister2() { + final Phaser phaser = new Phaser(1); + assertEquals(0, phaser.register()); + assertEquals(0, phaser.arrive()); + assertState(phaser, 0, 2, 1); + assertEquals(0, phaser.arriveAndDeregister()); + assertState(phaser, 1, 1, 1); + } + + /** + * arriveAndDeregister arrives at the barrier on a phaser with a parent and + * when a deregistration occurs and causes the phaser to have zero parties + * its parent will be deregistered as well + */ + public void testArriveAndDeregister3() { + Phaser parent = new Phaser(); + Phaser child = new Phaser(parent); + assertState(child, 0, 0, 0); + assertState(parent, 0, 0, 0); + assertEquals(0, child.register()); + assertState(child, 0, 1, 1); + assertState(parent, 0, 1, 1); + assertEquals(0, child.arriveAndDeregister()); + assertTerminated(child, 1); + assertTerminated(parent, 1); + } + + /** + * arriveAndDeregister deregisters one party from its parent when + * the number of parties of child is zero after deregistration + */ + public void testArriveAndDeregister4() { + Phaser parent = new Phaser(); + Phaser child = new Phaser(parent); + assertEquals(0, parent.register()); + assertEquals(0, child.register()); + assertState(child, 0, 1, 1); + assertState(parent, 0, 2, 2); + assertEquals(0, child.arriveAndDeregister()); + assertState(child, 0, 0, 0); + assertState(parent, 0, 1, 1); + } + + /** + * arriveAndDeregister deregisters one party from its parent when + * the number of parties of root is nonzero after deregistration. + */ + public void testArriveAndDeregister5() { + Phaser root = new Phaser(); + Phaser parent = new Phaser(root); + Phaser child = new Phaser(parent); + assertState(root, 0, 0, 0); + assertState(parent, 0, 0, 0); + assertState(child, 0, 0, 0); + assertEquals(0, child.register()); + assertState(root, 0, 1, 1); + assertState(parent, 0, 1, 1); + assertState(child, 0, 1, 1); + assertEquals(0, child.arriveAndDeregister()); + assertTerminated(child, 1); + assertTerminated(parent, 1); + assertTerminated(root, 1); + } + + /** + * arriveAndDeregister returns the phase in which it leaves the + * phaser in after deregistration + */ + public void testArriveAndDeregister6() { + final Phaser phaser = new Phaser(2); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + assertEquals(0, phaser.arrive()); + }}); + assertEquals(1, phaser.arriveAndAwaitAdvance()); + assertState(phaser, 1, 2, 2); + assertEquals(1, phaser.arriveAndDeregister()); + assertState(phaser, 1, 1, 1); + assertEquals(1, phaser.arriveAndDeregister()); + assertTerminated(phaser, 2); + awaitTermination(t); + } + + /** + * awaitAdvance succeeds upon advance + */ + public void testAwaitAdvance1() { + final Phaser phaser = new Phaser(1); + assertEquals(0, phaser.arrive()); + assertEquals(1, phaser.awaitAdvance(0)); + } + + /** + * awaitAdvance with a negative parameter will return without affecting the + * phaser + */ + public void testAwaitAdvance2() { + Phaser phaser = new Phaser(); + assertTrue(phaser.awaitAdvance(-1) < 0); + assertState(phaser, 0, 0, 0); + } + + /** + * awaitAdvanceInterruptibly blocks interruptibly + */ + public void testAwaitAdvanceInterruptibly_interruptible() throws InterruptedException { + final Phaser phaser = new Phaser(1); + final CountDownLatch pleaseInterrupt = new CountDownLatch(2); + + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() { + Thread.currentThread().interrupt(); + try { + phaser.awaitAdvanceInterruptibly(0); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + phaser.awaitAdvanceInterruptibly(0); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() throws TimeoutException { + Thread.currentThread().interrupt(); + try { + phaser.awaitAdvanceInterruptibly(0, 2*LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + phaser.awaitAdvanceInterruptibly(0, 2*LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseInterrupt); + assertState(phaser, 0, 1, 1); + assertThreadsStayAlive(t1, t2); + t1.interrupt(); + t2.interrupt(); + awaitTermination(t1); + awaitTermination(t2); + assertState(phaser, 0, 1, 1); + assertEquals(0, phaser.arrive()); + assertState(phaser, 1, 1, 1); + } + + /** + * awaitAdvance continues waiting if interrupted before waiting + */ + public void testAwaitAdvanceAfterInterrupt() { + final Phaser phaser = new Phaser(); + assertEquals(0, phaser.register()); + final CountDownLatch pleaseArrive = new CountDownLatch(1); + + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + Thread.currentThread().interrupt(); + assertEquals(0, phaser.register()); + assertEquals(0, phaser.arrive()); + pleaseArrive.countDown(); + assertTrue(Thread.currentThread().isInterrupted()); + assertEquals(1, phaser.awaitAdvance(0)); + assertTrue(Thread.interrupted()); + }}); + + await(pleaseArrive); + waitForThreadToEnterWaitState(t, SHORT_DELAY_MS); + assertEquals(0, phaser.arrive()); + awaitTermination(t); + + Thread.currentThread().interrupt(); + assertEquals(1, phaser.awaitAdvance(0)); + assertTrue(Thread.interrupted()); + } + + /** + * awaitAdvance continues waiting if interrupted while waiting + */ + public void testAwaitAdvanceBeforeInterrupt() { + final Phaser phaser = new Phaser(); + assertEquals(0, phaser.register()); + final CountDownLatch pleaseArrive = new CountDownLatch(1); + + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + assertEquals(0, phaser.register()); + assertEquals(0, phaser.arrive()); + assertFalse(Thread.currentThread().isInterrupted()); + pleaseArrive.countDown(); + assertEquals(1, phaser.awaitAdvance(0)); + assertTrue(Thread.interrupted()); + }}); + + await(pleaseArrive); + waitForThreadToEnterWaitState(t, SHORT_DELAY_MS); + t.interrupt(); + assertEquals(0, phaser.arrive()); + awaitTermination(t); + + Thread.currentThread().interrupt(); + assertEquals(1, phaser.awaitAdvance(0)); + assertTrue(Thread.interrupted()); + } + + /** + * arriveAndAwaitAdvance continues waiting if interrupted before waiting + */ + public void testArriveAndAwaitAdvanceAfterInterrupt() { + final Phaser phaser = new Phaser(); + assertEquals(0, phaser.register()); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + Thread.currentThread().interrupt(); + assertEquals(0, phaser.register()); + pleaseInterrupt.countDown(); + assertTrue(Thread.currentThread().isInterrupted()); + assertEquals(1, phaser.arriveAndAwaitAdvance()); + assertTrue(Thread.currentThread().isInterrupted()); + }}); + + await(pleaseInterrupt); + waitForThreadToEnterWaitState(t, SHORT_DELAY_MS); + Thread.currentThread().interrupt(); + assertEquals(1, phaser.arriveAndAwaitAdvance()); + assertTrue(Thread.interrupted()); + awaitTermination(t); + } + + /** + * arriveAndAwaitAdvance continues waiting if interrupted while waiting + */ + public void testArriveAndAwaitAdvanceBeforeInterrupt() { + final Phaser phaser = new Phaser(); + assertEquals(0, phaser.register()); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + assertEquals(0, phaser.register()); + assertFalse(Thread.currentThread().isInterrupted()); + pleaseInterrupt.countDown(); + assertEquals(1, phaser.arriveAndAwaitAdvance()); + assertTrue(Thread.currentThread().isInterrupted()); + }}); + + await(pleaseInterrupt); + waitForThreadToEnterWaitState(t, SHORT_DELAY_MS); + t.interrupt(); + Thread.currentThread().interrupt(); + assertEquals(1, phaser.arriveAndAwaitAdvance()); + assertTrue(Thread.interrupted()); + awaitTermination(t); + } + + /** + * awaitAdvance atomically waits for all parties within the same phase to + * complete before continuing + */ + public void testAwaitAdvance4() { + final Phaser phaser = new Phaser(4); + final AtomicInteger count = new AtomicInteger(0); + List threads = new ArrayList(); + for (int i = 0; i < 4; i++) + threads.add(newStartedThread(new CheckedRunnable() { + public void realRun() { + for (int k = 0; k < 3; k++) { + assertEquals(2 * k + 1, phaser.arriveAndAwaitAdvance()); + count.incrementAndGet(); + assertEquals(2 * k + 1, phaser.arrive()); + assertEquals(2 * k + 2, phaser.awaitAdvance(2 * k + 1)); + assertEquals(4 * (k + 1), count.get()); + }}})); + + for (Thread thread : threads) + awaitTermination(thread); + } + + /** + * awaitAdvance returns the current phase + */ + public void testAwaitAdvance5() { + final Phaser phaser = new Phaser(1); + assertEquals(1, phaser.awaitAdvance(phaser.arrive())); + assertEquals(1, phaser.getPhase()); + assertEquals(1, phaser.register()); + List threads = new ArrayList(); + for (int i = 0; i < 8; i++) { + final CountDownLatch latch = new CountDownLatch(1); + final boolean goesFirst = ((i & 1) == 0); + threads.add(newStartedThread(new CheckedRunnable() { + public void realRun() { + if (goesFirst) + latch.countDown(); + else + await(latch); + phaser.arrive(); + }})); + if (goesFirst) + await(latch); + else + latch.countDown(); + assertEquals(i + 2, phaser.awaitAdvance(phaser.arrive())); + assertEquals(i + 2, phaser.getPhase()); + } + for (Thread thread : threads) + awaitTermination(thread); + } + + /** + * awaitAdvance returns the current phase in child phasers + */ + public void testAwaitAdvanceTieredPhaser() throws Exception { + final Phaser parent = new Phaser(); + final List zeroPartyChildren = new ArrayList(3); + final List onePartyChildren = new ArrayList(3); + for (int i = 0; i < 3; i++) { + zeroPartyChildren.add(new Phaser(parent, 0)); + onePartyChildren.add(new Phaser(parent, 1)); + } + final List phasers = new ArrayList(); + phasers.addAll(zeroPartyChildren); + phasers.addAll(onePartyChildren); + phasers.add(parent); + for (Phaser phaser : phasers) { + assertEquals(-42, phaser.awaitAdvance(-42)); + assertEquals(-42, phaser.awaitAdvanceInterruptibly(-42)); + assertEquals(-42, phaser.awaitAdvanceInterruptibly(-42, MEDIUM_DELAY_MS, MILLISECONDS)); + } + + for (Phaser child : onePartyChildren) + assertEquals(0, child.arrive()); + for (Phaser phaser : phasers) { + assertEquals(-42, phaser.awaitAdvance(-42)); + assertEquals(-42, phaser.awaitAdvanceInterruptibly(-42)); + assertEquals(-42, phaser.awaitAdvanceInterruptibly(-42, MEDIUM_DELAY_MS, MILLISECONDS)); + assertEquals(1, phaser.awaitAdvance(0)); + assertEquals(1, phaser.awaitAdvanceInterruptibly(0)); + assertEquals(1, phaser.awaitAdvanceInterruptibly(0, MEDIUM_DELAY_MS, MILLISECONDS)); + } + + for (Phaser child : onePartyChildren) + assertEquals(1, child.arrive()); + for (Phaser phaser : phasers) { + assertEquals(-42, phaser.awaitAdvance(-42)); + assertEquals(-42, phaser.awaitAdvanceInterruptibly(-42)); + assertEquals(-42, phaser.awaitAdvanceInterruptibly(-42, MEDIUM_DELAY_MS, MILLISECONDS)); + assertEquals(2, phaser.awaitAdvance(0)); + assertEquals(2, phaser.awaitAdvanceInterruptibly(0)); + assertEquals(2, phaser.awaitAdvanceInterruptibly(0, MEDIUM_DELAY_MS, MILLISECONDS)); + assertEquals(2, phaser.awaitAdvance(1)); + assertEquals(2, phaser.awaitAdvanceInterruptibly(1)); + assertEquals(2, phaser.awaitAdvanceInterruptibly(1, MEDIUM_DELAY_MS, MILLISECONDS)); + } + } + + /** + * awaitAdvance returns when the phaser is externally terminated + */ + public void testAwaitAdvance6() { + final Phaser phaser = new Phaser(3); + final CountDownLatch pleaseForceTermination = new CountDownLatch(2); + final List threads = new ArrayList(); + for (int i = 0; i < 2; i++) { + Runnable r = new CheckedRunnable() { + public void realRun() { + assertEquals(0, phaser.arrive()); + pleaseForceTermination.countDown(); + assertTrue(phaser.awaitAdvance(0) < 0); + assertTrue(phaser.isTerminated()); + assertTrue(phaser.getPhase() < 0); + assertEquals(0, phaser.getPhase() + Integer.MIN_VALUE); + assertEquals(3, phaser.getRegisteredParties()); + }}; + threads.add(newStartedThread(r)); + } + await(pleaseForceTermination); + phaser.forceTermination(); + assertTrue(phaser.isTerminated()); + assertEquals(0, phaser.getPhase() + Integer.MIN_VALUE); + for (Thread thread : threads) + awaitTermination(thread); + assertEquals(3, phaser.getRegisteredParties()); + } + + /** + * arriveAndAwaitAdvance throws IllegalStateException with no + * unarrived parties + */ + public void testArriveAndAwaitAdvance1() { + Phaser phaser = new Phaser(); + try { + phaser.arriveAndAwaitAdvance(); + shouldThrow(); + } catch (IllegalStateException success) {} + } + + /** + * arriveAndAwaitAdvance waits for all threads to arrive, the + * number of arrived parties is the same number that is accounted + * for when the main thread awaitsAdvance + */ + public void testArriveAndAwaitAdvance3() { + final Phaser phaser = new Phaser(1); + final int THREADS = 3; + final CountDownLatch pleaseArrive = new CountDownLatch(THREADS); + final List threads = new ArrayList(); + for (int i = 0; i < THREADS; i++) + threads.add(newStartedThread(new CheckedRunnable() { + public void realRun() { + assertEquals(0, phaser.register()); + pleaseArrive.countDown(); + assertEquals(1, phaser.arriveAndAwaitAdvance()); + }})); + + await(pleaseArrive); + long startTime = System.nanoTime(); + while (phaser.getArrivedParties() < THREADS) + Thread.yield(); + assertEquals(THREADS, phaser.getArrivedParties()); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + for (Thread thread : threads) + waitForThreadToEnterWaitState(thread, SHORT_DELAY_MS); + for (Thread thread : threads) + assertTrue(thread.isAlive()); + assertState(phaser, 0, THREADS + 1, 1); + phaser.arriveAndAwaitAdvance(); + for (Thread thread : threads) + awaitTermination(thread); + assertState(phaser, 1, THREADS + 1, THREADS + 1); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/PriorityBlockingQueueTest.java b/jdk/test/java/util/concurrent/tck/PriorityBlockingQueueTest.java new file mode 100644 index 00000000000..c586837720a --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/PriorityBlockingQueueTest.java @@ -0,0 +1,764 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.PriorityBlockingQueue; + +import junit.framework.Test; + +public class PriorityBlockingQueueTest extends JSR166TestCase { + + public static class Generic extends BlockingQueueTest { + protected BlockingQueue emptyCollection() { + return new PriorityBlockingQueue(); + } + } + + public static class InitialCapacity extends BlockingQueueTest { + protected BlockingQueue emptyCollection() { + return new PriorityBlockingQueue(SIZE); + } + } + + public static void main(String[] args) { + main(suite(), args); + } + + public static Test suite() { + return newTestSuite(PriorityBlockingQueueTest.class, + new Generic().testSuite(), + new InitialCapacity().testSuite()); + } + + /** Sample Comparator */ + static class MyReverseComparator implements Comparator { + public int compare(Object x, Object y) { + return ((Comparable)y).compareTo(x); + } + } + + /** + * Returns a new queue of given size containing consecutive + * Integers 0 ... n. + */ + private PriorityBlockingQueue populatedQueue(int n) { + PriorityBlockingQueue q = + new PriorityBlockingQueue(n); + assertTrue(q.isEmpty()); + for (int i = n - 1; i >= 0; i -= 2) + assertTrue(q.offer(new Integer(i))); + for (int i = (n & 1); i < n; i += 2) + assertTrue(q.offer(new Integer(i))); + assertFalse(q.isEmpty()); + assertEquals(Integer.MAX_VALUE, q.remainingCapacity()); + assertEquals(n, q.size()); + return q; + } + + /** + * A new queue has unbounded capacity + */ + public void testConstructor1() { + assertEquals(Integer.MAX_VALUE, + new PriorityBlockingQueue(SIZE).remainingCapacity()); + } + + /** + * Constructor throws IAE if capacity argument nonpositive + */ + public void testConstructor2() { + try { + new PriorityBlockingQueue(0); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Initializing from null Collection throws NPE + */ + public void testConstructor3() { + try { + new PriorityBlockingQueue(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing from Collection of null elements throws NPE + */ + public void testConstructor4() { + Collection elements = Arrays.asList(new Integer[SIZE]); + try { + new PriorityBlockingQueue(elements); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing from Collection with some null elements throws NPE + */ + public void testConstructor5() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = i; + Collection elements = Arrays.asList(ints); + try { + new PriorityBlockingQueue(elements); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Queue contains all elements of collection used to initialize + */ + public void testConstructor6() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = i; + PriorityBlockingQueue q = new PriorityBlockingQueue(Arrays.asList(ints)); + for (int i = 0; i < SIZE; ++i) + assertEquals(ints[i], q.poll()); + } + + /** + * The comparator used in constructor is used + */ + public void testConstructor7() { + MyReverseComparator cmp = new MyReverseComparator(); + PriorityBlockingQueue q = new PriorityBlockingQueue(SIZE, cmp); + assertEquals(cmp, q.comparator()); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(i); + q.addAll(Arrays.asList(ints)); + for (int i = SIZE - 1; i >= 0; --i) + assertEquals(ints[i], q.poll()); + } + + /** + * isEmpty is true before add, false after + */ + public void testEmpty() { + PriorityBlockingQueue q = new PriorityBlockingQueue(2); + assertTrue(q.isEmpty()); + assertEquals(Integer.MAX_VALUE, q.remainingCapacity()); + q.add(one); + assertFalse(q.isEmpty()); + q.add(two); + q.remove(); + q.remove(); + assertTrue(q.isEmpty()); + } + + /** + * remainingCapacity() always returns Integer.MAX_VALUE + */ + public void testRemainingCapacity() { + BlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(Integer.MAX_VALUE, q.remainingCapacity()); + assertEquals(SIZE - i, q.size()); + assertEquals(i, q.remove()); + } + for (int i = 0; i < SIZE; ++i) { + assertEquals(Integer.MAX_VALUE, q.remainingCapacity()); + assertEquals(i, q.size()); + assertTrue(q.add(i)); + } + } + + /** + * Offer of comparable element succeeds + */ + public void testOffer() { + PriorityBlockingQueue q = new PriorityBlockingQueue(1); + assertTrue(q.offer(zero)); + assertTrue(q.offer(one)); + } + + /** + * Offer of non-Comparable throws CCE + */ + public void testOfferNonComparable() { + PriorityBlockingQueue q = new PriorityBlockingQueue(1); + try { + q.offer(new Object()); + q.offer(new Object()); + shouldThrow(); + } catch (ClassCastException success) {} + } + + /** + * add of comparable succeeds + */ + public void testAdd() { + PriorityBlockingQueue q = new PriorityBlockingQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.size()); + assertTrue(q.add(new Integer(i))); + } + } + + /** + * addAll(this) throws IAE + */ + public void testAddAllSelf() { + PriorityBlockingQueue q = populatedQueue(SIZE); + try { + q.addAll(q); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * addAll of a collection with any null elements throws NPE after + * possibly adding some elements + */ + public void testAddAll3() { + PriorityBlockingQueue q = new PriorityBlockingQueue(SIZE); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); + try { + q.addAll(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Queue contains all elements of successful addAll + */ + public void testAddAll5() { + Integer[] empty = new Integer[0]; + Integer[] ints = new Integer[SIZE]; + for (int i = SIZE - 1; i >= 0; --i) + ints[i] = new Integer(i); + PriorityBlockingQueue q = new PriorityBlockingQueue(SIZE); + assertFalse(q.addAll(Arrays.asList(empty))); + assertTrue(q.addAll(Arrays.asList(ints))); + for (int i = 0; i < SIZE; ++i) + assertEquals(ints[i], q.poll()); + } + + /** + * all elements successfully put are contained + */ + public void testPut() { + PriorityBlockingQueue q = new PriorityBlockingQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + Integer x = new Integer(i); + q.put(x); + assertTrue(q.contains(x)); + } + assertEquals(SIZE, q.size()); + } + + /** + * put doesn't block waiting for take + */ + public void testPutWithTake() throws InterruptedException { + final PriorityBlockingQueue q = new PriorityBlockingQueue(2); + final int size = 4; + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + for (int i = 0; i < size; i++) + q.put(new Integer(0)); + }}); + + awaitTermination(t); + assertEquals(size, q.size()); + q.take(); + } + + /** + * timed offer does not time out + */ + public void testTimedOffer() throws InterruptedException { + final PriorityBlockingQueue q = new PriorityBlockingQueue(2); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + q.put(new Integer(0)); + q.put(new Integer(0)); + assertTrue(q.offer(new Integer(0), SHORT_DELAY_MS, MILLISECONDS)); + assertTrue(q.offer(new Integer(0), LONG_DELAY_MS, MILLISECONDS)); + }}); + + awaitTermination(t); + } + + /** + * take retrieves elements in priority order + */ + public void testTake() throws InterruptedException { + PriorityBlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.take()); + } + } + + /** + * Take removes existing elements until empty, then blocks interruptibly + */ + public void testBlockingTake() throws InterruptedException { + final PriorityBlockingQueue q = populatedQueue(SIZE); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.take()); + } + + Thread.currentThread().interrupt(); + try { + q.take(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + q.take(); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * poll succeeds unless empty + */ + public void testPoll() { + PriorityBlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.poll()); + } + assertNull(q.poll()); + } + + /** + * timed poll with zero timeout succeeds when non-empty, else times out + */ + public void testTimedPoll0() throws InterruptedException { + PriorityBlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.poll(0, MILLISECONDS)); + } + assertNull(q.poll(0, MILLISECONDS)); + } + + /** + * timed poll with nonzero timeout succeeds when non-empty, else times out + */ + public void testTimedPoll() throws InterruptedException { + PriorityBlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + long startTime = System.nanoTime(); + assertEquals(i, (int) q.poll(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + long startTime = System.nanoTime(); + assertNull(q.poll(timeoutMillis(), MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + checkEmpty(q); + } + + /** + * Interrupted timed poll throws InterruptedException instead of + * returning timeout status + */ + public void testInterruptedTimedPoll() throws InterruptedException { + final BlockingQueue q = populatedQueue(SIZE); + final CountDownLatch aboutToWait = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, (int) q.poll(LONG_DELAY_MS, MILLISECONDS)); + } + aboutToWait.countDown(); + try { + q.poll(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) { + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + }}); + + aboutToWait.await(); + waitForThreadToEnterWaitState(t, LONG_DELAY_MS); + t.interrupt(); + awaitTermination(t); + } + + /** + * peek returns next element, or null if empty + */ + public void testPeek() { + PriorityBlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.peek()); + assertEquals(i, q.poll()); + assertTrue(q.peek() == null || + !q.peek().equals(i)); + } + assertNull(q.peek()); + } + + /** + * element returns next element, or throws NSEE if empty + */ + public void testElement() { + PriorityBlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.element()); + assertEquals(i, q.poll()); + } + try { + q.element(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * remove removes next element, or throws NSEE if empty + */ + public void testRemove() { + PriorityBlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.remove()); + } + try { + q.remove(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * contains(x) reports true when elements added but not yet removed + */ + public void testContains() { + PriorityBlockingQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.contains(new Integer(i))); + q.poll(); + assertFalse(q.contains(new Integer(i))); + } + } + + /** + * clear removes all elements + */ + public void testClear() { + PriorityBlockingQueue q = populatedQueue(SIZE); + q.clear(); + assertTrue(q.isEmpty()); + assertEquals(0, q.size()); + q.add(one); + assertFalse(q.isEmpty()); + assertTrue(q.contains(one)); + q.clear(); + assertTrue(q.isEmpty()); + } + + /** + * containsAll(c) is true when c contains a subset of elements + */ + public void testContainsAll() { + PriorityBlockingQueue q = populatedQueue(SIZE); + PriorityBlockingQueue p = new PriorityBlockingQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.containsAll(p)); + assertFalse(p.containsAll(q)); + p.add(new Integer(i)); + } + assertTrue(p.containsAll(q)); + } + + /** + * retainAll(c) retains only those elements of c and reports true if changed + */ + public void testRetainAll() { + PriorityBlockingQueue q = populatedQueue(SIZE); + PriorityBlockingQueue p = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + boolean changed = q.retainAll(p); + if (i == 0) + assertFalse(changed); + else + assertTrue(changed); + + assertTrue(q.containsAll(p)); + assertEquals(SIZE - i, q.size()); + p.remove(); + } + } + + /** + * removeAll(c) removes only those elements of c and reports true if changed + */ + public void testRemoveAll() { + for (int i = 1; i < SIZE; ++i) { + PriorityBlockingQueue q = populatedQueue(SIZE); + PriorityBlockingQueue p = populatedQueue(i); + assertTrue(q.removeAll(p)); + assertEquals(SIZE - i, q.size()); + for (int j = 0; j < i; ++j) { + Integer x = (Integer)(p.remove()); + assertFalse(q.contains(x)); + } + } + } + + /** + * toArray contains all elements + */ + public void testToArray() throws InterruptedException { + PriorityBlockingQueue q = populatedQueue(SIZE); + Object[] o = q.toArray(); + Arrays.sort(o); + for (int i = 0; i < o.length; i++) + assertSame(o[i], q.take()); + } + + /** + * toArray(a) contains all elements + */ + public void testToArray2() throws InterruptedException { + PriorityBlockingQueue q = populatedQueue(SIZE); + Integer[] ints = new Integer[SIZE]; + Integer[] array = q.toArray(ints); + assertSame(ints, array); + Arrays.sort(ints); + for (int i = 0; i < ints.length; i++) + assertSame(ints[i], q.take()); + } + + /** + * toArray(incompatible array type) throws ArrayStoreException + */ + public void testToArray1_BadArg() { + PriorityBlockingQueue q = populatedQueue(SIZE); + try { + q.toArray(new String[10]); + shouldThrow(); + } catch (ArrayStoreException success) {} + } + + /** + * iterator iterates through all elements + */ + public void testIterator() { + PriorityBlockingQueue q = populatedQueue(SIZE); + Iterator it = q.iterator(); + int i; + for (i = 0; it.hasNext(); i++) + assertTrue(q.contains(it.next())); + assertEquals(i, SIZE); + assertIteratorExhausted(it); + } + + /** + * iterator of empty collection has no elements + */ + public void testEmptyIterator() { + assertIteratorExhausted(new PriorityBlockingQueue().iterator()); + } + + /** + * iterator.remove removes current element + */ + public void testIteratorRemove() { + final PriorityBlockingQueue q = new PriorityBlockingQueue(3); + q.add(new Integer(2)); + q.add(new Integer(1)); + q.add(new Integer(3)); + + Iterator it = q.iterator(); + it.next(); + it.remove(); + + it = q.iterator(); + assertEquals(it.next(), new Integer(2)); + assertEquals(it.next(), new Integer(3)); + assertFalse(it.hasNext()); + } + + /** + * toString contains toStrings of elements + */ + public void testToString() { + PriorityBlockingQueue q = populatedQueue(SIZE); + String s = q.toString(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(s.contains(String.valueOf(i))); + } + } + + /** + * timed poll transfers elements across Executor tasks + */ + public void testPollInExecutor() { + final PriorityBlockingQueue q = new PriorityBlockingQueue(2); + final CheckedBarrier threadsStarted = new CheckedBarrier(2); + final ExecutorService executor = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(executor)) { + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertNull(q.poll()); + threadsStarted.await(); + assertSame(one, q.poll(LONG_DELAY_MS, MILLISECONDS)); + checkEmpty(q); + }}); + + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + q.put(one); + }}); + } + } + + /** + * A deserialized serialized queue has same elements + */ + public void testSerialization() throws Exception { + Queue x = populatedQueue(SIZE); + Queue y = serialClone(x); + + assertNotSame(x, y); + assertEquals(x.size(), y.size()); + while (!x.isEmpty()) { + assertFalse(y.isEmpty()); + assertEquals(x.remove(), y.remove()); + } + assertTrue(y.isEmpty()); + } + + /** + * drainTo(c) empties queue into another collection c + */ + public void testDrainTo() { + PriorityBlockingQueue q = populatedQueue(SIZE); + ArrayList l = new ArrayList(); + q.drainTo(l); + assertEquals(0, q.size()); + assertEquals(SIZE, l.size()); + for (int i = 0; i < SIZE; ++i) + assertEquals(l.get(i), new Integer(i)); + q.add(zero); + q.add(one); + assertFalse(q.isEmpty()); + assertTrue(q.contains(zero)); + assertTrue(q.contains(one)); + l.clear(); + q.drainTo(l); + assertEquals(0, q.size()); + assertEquals(2, l.size()); + for (int i = 0; i < 2; ++i) + assertEquals(l.get(i), new Integer(i)); + } + + /** + * drainTo empties queue + */ + public void testDrainToWithActivePut() throws InterruptedException { + final PriorityBlockingQueue q = populatedQueue(SIZE); + Thread t = new Thread(new CheckedRunnable() { + public void realRun() { + q.put(new Integer(SIZE + 1)); + }}); + + t.start(); + ArrayList l = new ArrayList(); + q.drainTo(l); + assertTrue(l.size() >= SIZE); + for (int i = 0; i < SIZE; ++i) + assertEquals(l.get(i), new Integer(i)); + t.join(); + assertTrue(q.size() + l.size() >= SIZE); + } + + /** + * drainTo(c, n) empties first min(n, size) elements of queue into c + */ + public void testDrainToN() { + PriorityBlockingQueue q = new PriorityBlockingQueue(SIZE * 2); + for (int i = 0; i < SIZE + 2; ++i) { + for (int j = 0; j < SIZE; j++) + assertTrue(q.offer(new Integer(j))); + ArrayList l = new ArrayList(); + q.drainTo(l, i); + int k = (i < SIZE) ? i : SIZE; + assertEquals(k, l.size()); + assertEquals(SIZE - k, q.size()); + for (int j = 0; j < k; ++j) + assertEquals(l.get(j), new Integer(j)); + do {} while (q.poll() != null); + } + } + + /** + * remove(null), contains(null) always return false + */ + public void testNeverContainsNull() { + Collection[] qs = { + new PriorityBlockingQueue(), + populatedQueue(2), + }; + + for (Collection q : qs) { + assertFalse(q.contains(null)); + assertFalse(q.remove(null)); + } + } + +} diff --git a/jdk/test/java/util/concurrent/tck/PriorityQueueTest.java b/jdk/test/java/util/concurrent/tck/PriorityQueueTest.java new file mode 100644 index 00000000000..edb2379e2ec --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/PriorityQueueTest.java @@ -0,0 +1,528 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.PriorityQueue; +import java.util.Queue; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class PriorityQueueTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(PriorityQueueTest.class); + } + + static class MyReverseComparator implements Comparator { + public int compare(Object x, Object y) { + return ((Comparable)y).compareTo(x); + } + } + + /** + * Returns a new queue of given size containing consecutive + * Integers 0 ... n. + */ + private PriorityQueue populatedQueue(int n) { + PriorityQueue q = new PriorityQueue(n); + assertTrue(q.isEmpty()); + for (int i = n - 1; i >= 0; i -= 2) + assertTrue(q.offer(new Integer(i))); + for (int i = (n & 1); i < n; i += 2) + assertTrue(q.offer(new Integer(i))); + assertFalse(q.isEmpty()); + assertEquals(n, q.size()); + return q; + } + + /** + * A new queue has unbounded capacity + */ + public void testConstructor1() { + assertEquals(0, new PriorityQueue(SIZE).size()); + } + + /** + * Constructor throws IAE if capacity argument nonpositive + */ + public void testConstructor2() { + try { + new PriorityQueue(0); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Initializing from null Collection throws NPE + */ + public void testConstructor3() { + try { + new PriorityQueue((Collection)null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing from Collection of null elements throws NPE + */ + public void testConstructor4() { + try { + new PriorityQueue(Arrays.asList(new Integer[SIZE])); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing from Collection with some null elements throws NPE + */ + public void testConstructor5() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); + try { + new PriorityQueue(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Queue contains all elements of collection used to initialize + */ + public void testConstructor6() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(i); + PriorityQueue q = new PriorityQueue(Arrays.asList(ints)); + for (int i = 0; i < SIZE; ++i) + assertEquals(ints[i], q.poll()); + } + + /** + * The comparator used in constructor is used + */ + public void testConstructor7() { + MyReverseComparator cmp = new MyReverseComparator(); + PriorityQueue q = new PriorityQueue(SIZE, cmp); + assertEquals(cmp, q.comparator()); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(i); + q.addAll(Arrays.asList(ints)); + for (int i = SIZE - 1; i >= 0; --i) + assertEquals(ints[i], q.poll()); + } + + /** + * isEmpty is true before add, false after + */ + public void testEmpty() { + PriorityQueue q = new PriorityQueue(2); + assertTrue(q.isEmpty()); + q.add(new Integer(1)); + assertFalse(q.isEmpty()); + q.add(new Integer(2)); + q.remove(); + q.remove(); + assertTrue(q.isEmpty()); + } + + /** + * size changes when elements added and removed + */ + public void testSize() { + PriorityQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(SIZE - i, q.size()); + q.remove(); + } + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.size()); + q.add(new Integer(i)); + } + } + + /** + * offer(null) throws NPE + */ + public void testOfferNull() { + PriorityQueue q = new PriorityQueue(1); + try { + q.offer(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * add(null) throws NPE + */ + public void testAddNull() { + PriorityQueue q = new PriorityQueue(1); + try { + q.add(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Offer of comparable element succeeds + */ + public void testOffer() { + PriorityQueue q = new PriorityQueue(1); + assertTrue(q.offer(zero)); + assertTrue(q.offer(one)); + } + + /** + * Offer of non-Comparable throws CCE + */ + public void testOfferNonComparable() { + PriorityQueue q = new PriorityQueue(1); + try { + q.offer(new Object()); + q.offer(new Object()); + shouldThrow(); + } catch (ClassCastException success) {} + } + + /** + * add of comparable succeeds + */ + public void testAdd() { + PriorityQueue q = new PriorityQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.size()); + assertTrue(q.add(new Integer(i))); + } + } + + /** + * addAll(null) throws NPE + */ + public void testAddAll1() { + PriorityQueue q = new PriorityQueue(1); + try { + q.addAll(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll of a collection with null elements throws NPE + */ + public void testAddAll2() { + PriorityQueue q = new PriorityQueue(SIZE); + try { + q.addAll(Arrays.asList(new Integer[SIZE])); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll of a collection with any null elements throws NPE after + * possibly adding some elements + */ + public void testAddAll3() { + PriorityQueue q = new PriorityQueue(SIZE); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); + try { + q.addAll(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Queue contains all elements of successful addAll + */ + public void testAddAll5() { + Integer[] empty = new Integer[0]; + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(SIZE - 1 - i); + PriorityQueue q = new PriorityQueue(SIZE); + assertFalse(q.addAll(Arrays.asList(empty))); + assertTrue(q.addAll(Arrays.asList(ints))); + for (int i = 0; i < SIZE; ++i) + assertEquals(new Integer(i), q.poll()); + } + + /** + * poll succeeds unless empty + */ + public void testPoll() { + PriorityQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.poll()); + } + assertNull(q.poll()); + } + + /** + * peek returns next element, or null if empty + */ + public void testPeek() { + PriorityQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.peek()); + assertEquals(i, q.poll()); + assertTrue(q.peek() == null || + !q.peek().equals(i)); + } + assertNull(q.peek()); + } + + /** + * element returns next element, or throws NSEE if empty + */ + public void testElement() { + PriorityQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.element()); + assertEquals(i, q.poll()); + } + try { + q.element(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * remove removes next element, or throws NSEE if empty + */ + public void testRemove() { + PriorityQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.remove()); + } + try { + q.remove(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * remove(x) removes x and returns true if present + */ + public void testRemoveElement() { + PriorityQueue q = populatedQueue(SIZE); + for (int i = 1; i < SIZE; i += 2) { + assertTrue(q.contains(i)); + assertTrue(q.remove(i)); + assertFalse(q.contains(i)); + assertTrue(q.contains(i - 1)); + } + for (int i = 0; i < SIZE; i += 2) { + assertTrue(q.contains(i)); + assertTrue(q.remove(i)); + assertFalse(q.contains(i)); + assertFalse(q.remove(i + 1)); + assertFalse(q.contains(i + 1)); + } + assertTrue(q.isEmpty()); + } + + /** + * contains(x) reports true when elements added but not yet removed + */ + public void testContains() { + PriorityQueue q = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.contains(new Integer(i))); + q.poll(); + assertFalse(q.contains(new Integer(i))); + } + } + + /** + * clear removes all elements + */ + public void testClear() { + PriorityQueue q = populatedQueue(SIZE); + q.clear(); + assertTrue(q.isEmpty()); + assertEquals(0, q.size()); + q.add(new Integer(1)); + assertFalse(q.isEmpty()); + q.clear(); + assertTrue(q.isEmpty()); + } + + /** + * containsAll(c) is true when c contains a subset of elements + */ + public void testContainsAll() { + PriorityQueue q = populatedQueue(SIZE); + PriorityQueue p = new PriorityQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.containsAll(p)); + assertFalse(p.containsAll(q)); + p.add(new Integer(i)); + } + assertTrue(p.containsAll(q)); + } + + /** + * retainAll(c) retains only those elements of c and reports true if changed + */ + public void testRetainAll() { + PriorityQueue q = populatedQueue(SIZE); + PriorityQueue p = populatedQueue(SIZE); + for (int i = 0; i < SIZE; ++i) { + boolean changed = q.retainAll(p); + if (i == 0) + assertFalse(changed); + else + assertTrue(changed); + + assertTrue(q.containsAll(p)); + assertEquals(SIZE - i, q.size()); + p.remove(); + } + } + + /** + * removeAll(c) removes only those elements of c and reports true if changed + */ + public void testRemoveAll() { + for (int i = 1; i < SIZE; ++i) { + PriorityQueue q = populatedQueue(SIZE); + PriorityQueue p = populatedQueue(i); + assertTrue(q.removeAll(p)); + assertEquals(SIZE - i, q.size()); + for (int j = 0; j < i; ++j) { + Integer x = (Integer)(p.remove()); + assertFalse(q.contains(x)); + } + } + } + + /** + * toArray contains all elements + */ + public void testToArray() { + PriorityQueue q = populatedQueue(SIZE); + Object[] o = q.toArray(); + Arrays.sort(o); + for (int i = 0; i < o.length; i++) + assertSame(o[i], q.poll()); + } + + /** + * toArray(a) contains all elements + */ + public void testToArray2() { + PriorityQueue q = populatedQueue(SIZE); + Integer[] ints = new Integer[SIZE]; + Integer[] array = q.toArray(ints); + assertSame(ints, array); + Arrays.sort(ints); + for (int i = 0; i < ints.length; i++) + assertSame(ints[i], q.poll()); + } + + /** + * iterator iterates through all elements + */ + public void testIterator() { + PriorityQueue q = populatedQueue(SIZE); + Iterator it = q.iterator(); + int i; + for (i = 0; it.hasNext(); i++) + assertTrue(q.contains(it.next())); + assertEquals(i, SIZE); + assertIteratorExhausted(it); + } + + /** + * iterator of empty collection has no elements + */ + public void testEmptyIterator() { + assertIteratorExhausted(new PriorityQueue().iterator()); + } + + /** + * iterator.remove removes current element + */ + public void testIteratorRemove() { + final PriorityQueue q = new PriorityQueue(3); + q.add(new Integer(2)); + q.add(new Integer(1)); + q.add(new Integer(3)); + + Iterator it = q.iterator(); + it.next(); + it.remove(); + + it = q.iterator(); + assertEquals(it.next(), new Integer(2)); + assertEquals(it.next(), new Integer(3)); + assertFalse(it.hasNext()); + } + + /** + * toString contains toStrings of elements + */ + public void testToString() { + PriorityQueue q = populatedQueue(SIZE); + String s = q.toString(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(s.contains(String.valueOf(i))); + } + } + + /** + * A deserialized serialized queue has same elements + */ + public void testSerialization() throws Exception { + Queue x = populatedQueue(SIZE); + Queue y = serialClone(x); + + assertNotSame(x, y); + assertEquals(x.size(), y.size()); + while (!x.isEmpty()) { + assertFalse(y.isEmpty()); + assertEquals(x.remove(), y.remove()); + } + assertTrue(y.isEmpty()); + } +} diff --git a/jdk/test/java/util/concurrent/tck/RecursiveActionTest.java b/jdk/test/java/util/concurrent/tck/RecursiveActionTest.java new file mode 100644 index 00000000000..9ce68c34166 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/RecursiveActionTest.java @@ -0,0 +1,1272 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import static java.util.concurrent.TimeUnit.SECONDS; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinTask; +import java.util.concurrent.ForkJoinWorkerThread; +import java.util.concurrent.RecursiveAction; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeoutException; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class RecursiveActionTest extends JSR166TestCase { + + public static void main(String[] args) { + main(suite(), args); + } + + public static Test suite() { + return new TestSuite(RecursiveActionTest.class); + } + + private static ForkJoinPool mainPool() { + return new ForkJoinPool(); + } + + private static ForkJoinPool singletonPool() { + return new ForkJoinPool(1); + } + + private static ForkJoinPool asyncSingletonPool() { + return new ForkJoinPool(1, + ForkJoinPool.defaultForkJoinWorkerThreadFactory, + null, true); + } + + private void testInvokeOnPool(ForkJoinPool pool, RecursiveAction a) { + try (PoolCleaner cleaner = cleaner(pool)) { + checkNotDone(a); + + assertNull(pool.invoke(a)); + + checkCompletedNormally(a); + } + } + + void checkNotDone(RecursiveAction a) { + assertFalse(a.isDone()); + assertFalse(a.isCompletedNormally()); + assertFalse(a.isCompletedAbnormally()); + assertFalse(a.isCancelled()); + assertNull(a.getException()); + assertNull(a.getRawResult()); + + if (! ForkJoinTask.inForkJoinPool()) { + Thread.currentThread().interrupt(); + try { + a.get(); + shouldThrow(); + } catch (InterruptedException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + + Thread.currentThread().interrupt(); + try { + a.get(5L, SECONDS); + shouldThrow(); + } catch (InterruptedException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + try { + a.get(0L, SECONDS); + shouldThrow(); + } catch (TimeoutException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + void checkCompletedNormally(RecursiveAction a) { + assertTrue(a.isDone()); + assertFalse(a.isCancelled()); + assertTrue(a.isCompletedNormally()); + assertFalse(a.isCompletedAbnormally()); + assertNull(a.getException()); + assertNull(a.getRawResult()); + assertNull(a.join()); + assertFalse(a.cancel(false)); + assertFalse(a.cancel(true)); + try { + assertNull(a.get()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + try { + assertNull(a.get(5L, SECONDS)); + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + void checkCancelled(RecursiveAction a) { + assertTrue(a.isDone()); + assertTrue(a.isCancelled()); + assertFalse(a.isCompletedNormally()); + assertTrue(a.isCompletedAbnormally()); + assertTrue(a.getException() instanceof CancellationException); + assertNull(a.getRawResult()); + + try { + a.join(); + shouldThrow(); + } catch (CancellationException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + + try { + a.get(); + shouldThrow(); + } catch (CancellationException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + + try { + a.get(5L, SECONDS); + shouldThrow(); + } catch (CancellationException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + void checkCompletedAbnormally(RecursiveAction a, Throwable t) { + assertTrue(a.isDone()); + assertFalse(a.isCancelled()); + assertFalse(a.isCompletedNormally()); + assertTrue(a.isCompletedAbnormally()); + assertSame(t.getClass(), a.getException().getClass()); + assertNull(a.getRawResult()); + assertFalse(a.cancel(false)); + assertFalse(a.cancel(true)); + + try { + a.join(); + shouldThrow(); + } catch (Throwable expected) { + assertSame(expected.getClass(), t.getClass()); + } + + try { + a.get(); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(t.getClass(), success.getCause().getClass()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + + try { + a.get(5L, SECONDS); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(t.getClass(), success.getCause().getClass()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + public static final class FJException extends RuntimeException { + public FJException() { super(); } + public FJException(Throwable cause) { super(cause); } + } + + // A simple recursive action for testing + final class FibAction extends CheckedRecursiveAction { + final int number; + int result; + FibAction(int n) { number = n; } + protected void realCompute() { + int n = number; + if (n <= 1) + result = n; + else { + FibAction f1 = new FibAction(n - 1); + FibAction f2 = new FibAction(n - 2); + invokeAll(f1, f2); + result = f1.result + f2.result; + } + } + } + + // A recursive action failing in base case + static final class FailingFibAction extends RecursiveAction { + final int number; + int result; + FailingFibAction(int n) { number = n; } + public void compute() { + int n = number; + if (n <= 1) + throw new FJException(); + else { + FailingFibAction f1 = new FailingFibAction(n - 1); + FailingFibAction f2 = new FailingFibAction(n - 2); + invokeAll(f1, f2); + result = f1.result + f2.result; + } + } + } + + /** + * invoke returns when task completes normally. + * isCompletedAbnormally and isCancelled return false for normally + * completed tasks. getRawResult of a RecursiveAction returns null; + */ + public void testInvoke() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + assertNull(f.invoke()); + assertEquals(21, f.result); + checkCompletedNormally(f); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * quietlyInvoke task returns when task completes normally. + * isCompletedAbnormally and isCancelled return false for normally + * completed tasks + */ + public void testQuietlyInvoke() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + f.quietlyInvoke(); + assertEquals(21, f.result); + checkCompletedNormally(f); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * join of a forked task returns when task completes + */ + public void testForkJoin() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + assertNull(f.join()); + assertEquals(21, f.result); + checkCompletedNormally(f); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * join/quietlyJoin of a forked task succeeds in the presence of interrupts + */ + public void testJoinIgnoresInterrupts() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + final Thread myself = Thread.currentThread(); + + // test join() + assertSame(f, f.fork()); + myself.interrupt(); + assertTrue(myself.isInterrupted()); + assertNull(f.join()); + Thread.interrupted(); + assertEquals(21, f.result); + checkCompletedNormally(f); + + f = new FibAction(8); + f.cancel(true); + assertSame(f, f.fork()); + myself.interrupt(); + assertTrue(myself.isInterrupted()); + try { + f.join(); + shouldThrow(); + } catch (CancellationException success) { + Thread.interrupted(); + checkCancelled(f); + } + + f = new FibAction(8); + f.completeExceptionally(new FJException()); + assertSame(f, f.fork()); + myself.interrupt(); + assertTrue(myself.isInterrupted()); + try { + f.join(); + shouldThrow(); + } catch (FJException success) { + Thread.interrupted(); + checkCompletedAbnormally(f, success); + } + + // test quietlyJoin() + f = new FibAction(8); + assertSame(f, f.fork()); + myself.interrupt(); + assertTrue(myself.isInterrupted()); + f.quietlyJoin(); + Thread.interrupted(); + assertEquals(21, f.result); + checkCompletedNormally(f); + + f = new FibAction(8); + f.cancel(true); + assertSame(f, f.fork()); + myself.interrupt(); + assertTrue(myself.isInterrupted()); + f.quietlyJoin(); + Thread.interrupted(); + checkCancelled(f); + + f = new FibAction(8); + f.completeExceptionally(new FJException()); + assertSame(f, f.fork()); + myself.interrupt(); + assertTrue(myself.isInterrupted()); + f.quietlyJoin(); + Thread.interrupted(); + checkCompletedAbnormally(f, f.getException()); + }}; + testInvokeOnPool(mainPool(), a); + a.reinitialize(); + testInvokeOnPool(singletonPool(), a); + } + + /** + * join/quietlyJoin of a forked task when not in ForkJoinPool + * succeeds in the presence of interrupts + */ + public void testJoinIgnoresInterruptsOutsideForkJoinPool() { + final SynchronousQueue sq = + new SynchronousQueue(); + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws InterruptedException { + FibAction[] fibActions = new FibAction[6]; + for (int i = 0; i < fibActions.length; i++) + fibActions[i] = new FibAction(8); + + fibActions[1].cancel(false); + fibActions[2].completeExceptionally(new FJException()); + fibActions[4].cancel(true); + fibActions[5].completeExceptionally(new FJException()); + + for (int i = 0; i < fibActions.length; i++) + fibActions[i].fork(); + + sq.put(fibActions); + + helpQuiesce(); + }}; + + Runnable r = new CheckedRunnable() { + public void realRun() throws InterruptedException { + FibAction[] fibActions = sq.take(); + FibAction f; + final Thread myself = Thread.currentThread(); + + // test join() ------------ + + f = fibActions[0]; + assertFalse(ForkJoinTask.inForkJoinPool()); + myself.interrupt(); + assertTrue(myself.isInterrupted()); + assertNull(f.join()); + assertTrue(Thread.interrupted()); + assertEquals(21, f.result); + checkCompletedNormally(f); + + f = fibActions[1]; + myself.interrupt(); + assertTrue(myself.isInterrupted()); + try { + f.join(); + shouldThrow(); + } catch (CancellationException success) { + assertTrue(Thread.interrupted()); + checkCancelled(f); + } + + f = fibActions[2]; + myself.interrupt(); + assertTrue(myself.isInterrupted()); + try { + f.join(); + shouldThrow(); + } catch (FJException success) { + assertTrue(Thread.interrupted()); + checkCompletedAbnormally(f, success); + } + + // test quietlyJoin() --------- + + f = fibActions[3]; + myself.interrupt(); + assertTrue(myself.isInterrupted()); + f.quietlyJoin(); + assertTrue(Thread.interrupted()); + assertEquals(21, f.result); + checkCompletedNormally(f); + + f = fibActions[4]; + myself.interrupt(); + assertTrue(myself.isInterrupted()); + f.quietlyJoin(); + assertTrue(Thread.interrupted()); + checkCancelled(f); + + f = fibActions[5]; + myself.interrupt(); + assertTrue(myself.isInterrupted()); + f.quietlyJoin(); + assertTrue(Thread.interrupted()); + assertTrue(f.getException() instanceof FJException); + checkCompletedAbnormally(f, f.getException()); + }}; + + Thread t; + + t = newStartedThread(r); + testInvokeOnPool(mainPool(), a); + awaitTermination(t); + + a.reinitialize(); + t = newStartedThread(r); + testInvokeOnPool(singletonPool(), a); + awaitTermination(t); + } + + /** + * get of a forked task returns when task completes + */ + public void testForkGet() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + assertNull(f.get()); + assertEquals(21, f.result); + checkCompletedNormally(f); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * timed get of a forked task returns when task completes + */ + public void testForkTimedGet() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + assertNull(f.get(5L, SECONDS)); + assertEquals(21, f.result); + checkCompletedNormally(f); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * timed get with null time unit throws NPE + */ + public void testForkTimedGetNPE() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + try { + f.get(5L, null); + shouldThrow(); + } catch (NullPointerException success) {} + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * quietlyJoin of a forked task returns when task completes + */ + public void testForkQuietlyJoin() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + f.quietlyJoin(); + assertEquals(21, f.result); + checkCompletedNormally(f); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * helpQuiesce returns when tasks are complete. + * getQueuedTaskCount returns 0 when quiescent + */ + public void testForkHelpQuiesce() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + helpQuiesce(); + while (!f.isDone()) // wait out race + ; + assertEquals(21, f.result); + assertEquals(0, getQueuedTaskCount()); + checkCompletedNormally(f); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invoke task throws exception when task completes abnormally + */ + public void testAbnormalInvoke() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingFibAction f = new FailingFibAction(8); + try { + f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * quietlyInvoke task returns when task completes abnormally + */ + public void testAbnormalQuietlyInvoke() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingFibAction f = new FailingFibAction(8); + f.quietlyInvoke(); + assertTrue(f.getException() instanceof FJException); + checkCompletedAbnormally(f, f.getException()); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * join of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkJoin() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingFibAction f = new FailingFibAction(8); + assertSame(f, f.fork()); + try { + f.join(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * get of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkGet() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FailingFibAction f = new FailingFibAction(8); + assertSame(f, f.fork()); + try { + f.get(); + shouldThrow(); + } catch (ExecutionException success) { + Throwable cause = success.getCause(); + assertTrue(cause instanceof FJException); + checkCompletedAbnormally(f, cause); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * timed get of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkTimedGet() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FailingFibAction f = new FailingFibAction(8); + assertSame(f, f.fork()); + try { + f.get(5L, SECONDS); + shouldThrow(); + } catch (ExecutionException success) { + Throwable cause = success.getCause(); + assertTrue(cause instanceof FJException); + checkCompletedAbnormally(f, cause); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * quietlyJoin of a forked task returns when task completes abnormally + */ + public void testAbnormalForkQuietlyJoin() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingFibAction f = new FailingFibAction(8); + assertSame(f, f.fork()); + f.quietlyJoin(); + assertTrue(f.getException() instanceof FJException); + checkCompletedAbnormally(f, f.getException()); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invoke task throws exception when task cancelled + */ + public void testCancelledInvoke() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + assertTrue(f.cancel(true)); + try { + f.invoke(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * join of a forked task throws exception when task cancelled + */ + public void testCancelledForkJoin() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.join(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * get of a forked task throws exception when task cancelled + */ + public void testCancelledForkGet() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FibAction f = new FibAction(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.get(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * timed get of a forked task throws exception when task cancelled + */ + public void testCancelledForkTimedGet() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() throws Exception { + FibAction f = new FibAction(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + f.get(5L, SECONDS); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * quietlyJoin of a forked task returns when task cancelled + */ + public void testCancelledForkQuietlyJoin() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + f.quietlyJoin(); + checkCancelled(f); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * getPool of executing task returns its pool + */ + public void testGetPool() { + final ForkJoinPool mainPool = mainPool(); + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + assertSame(mainPool, getPool()); + }}; + testInvokeOnPool(mainPool, a); + } + + /** + * getPool of non-FJ task returns null + */ + public void testGetPool2() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + assertNull(getPool()); + }}; + assertNull(a.invoke()); + } + + /** + * inForkJoinPool of executing task returns true + */ + public void testInForkJoinPool() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + assertTrue(inForkJoinPool()); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * inForkJoinPool of non-FJ task returns false + */ + public void testInForkJoinPool2() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + assertFalse(inForkJoinPool()); + }}; + assertNull(a.invoke()); + } + + /** + * getPool of current thread in pool returns its pool + */ + public void testWorkerGetPool() { + final ForkJoinPool mainPool = mainPool(); + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + ForkJoinWorkerThread w = + (ForkJoinWorkerThread) Thread.currentThread(); + assertSame(mainPool, w.getPool()); + }}; + testInvokeOnPool(mainPool, a); + } + + /** + * getPoolIndex of current thread in pool returns 0 <= value < poolSize + */ + public void testWorkerGetPoolIndex() { + final ForkJoinPool mainPool = mainPool(); + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + ForkJoinWorkerThread w = + (ForkJoinWorkerThread) Thread.currentThread(); + assertTrue(w.getPoolIndex() >= 0); + // pool size can shrink after assigning index, so cannot check + // assertTrue(w.getPoolIndex() < mainPool.getPoolSize()); + }}; + testInvokeOnPool(mainPool, a); + } + + /** + * setRawResult(null) succeeds + */ + public void testSetRawResult() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + setRawResult(null); + assertNull(getRawResult()); + }}; + assertNull(a.invoke()); + } + + /** + * A reinitialized normally completed task may be re-invoked + */ + public void testReinitialize() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + checkNotDone(f); + + for (int i = 0; i < 3; i++) { + assertNull(f.invoke()); + assertEquals(21, f.result); + checkCompletedNormally(f); + f.reinitialize(); + checkNotDone(f); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * A reinitialized abnormally completed task may be re-invoked + */ + public void testReinitializeAbnormal() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingFibAction f = new FailingFibAction(8); + checkNotDone(f); + + for (int i = 0; i < 3; i++) { + try { + f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + f.reinitialize(); + checkNotDone(f); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invoke task throws exception after invoking completeExceptionally + */ + public void testCompleteExceptionally() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + f.completeExceptionally(new FJException()); + try { + f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invoke task suppresses execution invoking complete + */ + public void testComplete() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + f.complete(null); + assertNull(f.invoke()); + assertEquals(0, f.result); + checkCompletedNormally(f); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(t1, t2) invokes all task arguments + */ + public void testInvokeAll2() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + FibAction g = new FibAction(9); + invokeAll(f, g); + checkCompletedNormally(f); + assertEquals(21, f.result); + checkCompletedNormally(g); + assertEquals(34, g.result); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(tasks) with 1 argument invokes task + */ + public void testInvokeAll1() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + invokeAll(f); + checkCompletedNormally(f); + assertEquals(21, f.result); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(tasks) with > 2 argument invokes tasks + */ + public void testInvokeAll3() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + FibAction g = new FibAction(9); + FibAction h = new FibAction(7); + invokeAll(f, g, h); + assertTrue(f.isDone()); + assertTrue(g.isDone()); + assertTrue(h.isDone()); + checkCompletedNormally(f); + assertEquals(21, f.result); + checkCompletedNormally(g); + assertEquals(34, g.result); + checkCompletedNormally(g); + assertEquals(13, h.result); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(collection) invokes all tasks in the collection + */ + public void testInvokeAllCollection() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + FibAction g = new FibAction(9); + FibAction h = new FibAction(7); + HashSet set = new HashSet(); + set.add(f); + set.add(g); + set.add(h); + invokeAll(set); + assertTrue(f.isDone()); + assertTrue(g.isDone()); + assertTrue(h.isDone()); + checkCompletedNormally(f); + assertEquals(21, f.result); + checkCompletedNormally(g); + assertEquals(34, g.result); + checkCompletedNormally(g); + assertEquals(13, h.result); + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(tasks) with any null task throws NPE + */ + public void testInvokeAllNPE() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + FibAction g = new FibAction(9); + FibAction h = null; + try { + invokeAll(f, g, h); + shouldThrow(); + } catch (NullPointerException success) {} + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(t1, t2) throw exception if any task does + */ + public void testAbnormalInvokeAll2() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + FailingFibAction g = new FailingFibAction(9); + try { + invokeAll(f, g); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(tasks) with 1 argument throws exception if task does + */ + public void testAbnormalInvokeAll1() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingFibAction g = new FailingFibAction(9); + try { + invokeAll(g); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(tasks) with > 2 argument throws exception if any task does + */ + public void testAbnormalInvokeAll3() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction f = new FibAction(8); + FailingFibAction g = new FailingFibAction(9); + FibAction h = new FibAction(7); + try { + invokeAll(f, g, h); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * invokeAll(collection) throws exception if any task does + */ + public void testAbnormalInvokeAllCollection() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FailingFibAction f = new FailingFibAction(8); + FibAction g = new FibAction(9); + FibAction h = new FibAction(7); + HashSet set = new HashSet(); + set.add(f); + set.add(g); + set.add(h); + try { + invokeAll(set); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + }}; + testInvokeOnPool(mainPool(), a); + } + + /** + * tryUnfork returns true for most recent unexecuted task, + * and suppresses execution + */ + public void testTryUnfork() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction g = new FibAction(9); + assertSame(g, g.fork()); + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + assertTrue(f.tryUnfork()); + helpQuiesce(); + checkNotDone(f); + checkCompletedNormally(g); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * getSurplusQueuedTaskCount returns > 0 when + * there are more tasks than threads + */ + public void testGetSurplusQueuedTaskCount() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction h = new FibAction(7); + assertSame(h, h.fork()); + FibAction g = new FibAction(9); + assertSame(g, g.fork()); + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + assertTrue(getSurplusQueuedTaskCount() > 0); + helpQuiesce(); + assertEquals(0, getSurplusQueuedTaskCount()); + checkCompletedNormally(f); + checkCompletedNormally(g); + checkCompletedNormally(h); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * peekNextLocalTask returns most recent unexecuted task. + */ + public void testPeekNextLocalTask() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction g = new FibAction(9); + assertSame(g, g.fork()); + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + assertSame(f, peekNextLocalTask()); + assertNull(f.join()); + checkCompletedNormally(f); + helpQuiesce(); + checkCompletedNormally(f); + checkCompletedNormally(g); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * pollNextLocalTask returns most recent unexecuted task + * without executing it + */ + public void testPollNextLocalTask() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction g = new FibAction(9); + assertSame(g, g.fork()); + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + assertSame(f, pollNextLocalTask()); + helpQuiesce(); + checkNotDone(f); + checkCompletedNormally(g); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * pollTask returns an unexecuted task without executing it + */ + public void testPollTask() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction g = new FibAction(9); + assertSame(g, g.fork()); + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + assertSame(f, pollTask()); + helpQuiesce(); + checkNotDone(f); + checkCompletedNormally(g); + }}; + testInvokeOnPool(singletonPool(), a); + } + + /** + * peekNextLocalTask returns least recent unexecuted task in async mode + */ + public void testPeekNextLocalTaskAsync() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction g = new FibAction(9); + assertSame(g, g.fork()); + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + assertSame(g, peekNextLocalTask()); + assertNull(f.join()); + helpQuiesce(); + checkCompletedNormally(f); + checkCompletedNormally(g); + }}; + testInvokeOnPool(asyncSingletonPool(), a); + } + + /** + * pollNextLocalTask returns least recent unexecuted task without + * executing it, in async mode + */ + public void testPollNextLocalTaskAsync() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction g = new FibAction(9); + assertSame(g, g.fork()); + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + assertSame(g, pollNextLocalTask()); + helpQuiesce(); + checkCompletedNormally(f); + checkNotDone(g); + }}; + testInvokeOnPool(asyncSingletonPool(), a); + } + + /** + * pollTask returns an unexecuted task without executing it, in + * async mode + */ + public void testPollTaskAsync() { + RecursiveAction a = new CheckedRecursiveAction() { + protected void realCompute() { + FibAction g = new FibAction(9); + assertSame(g, g.fork()); + FibAction f = new FibAction(8); + assertSame(f, f.fork()); + assertSame(g, pollTask()); + helpQuiesce(); + checkCompletedNormally(f); + checkNotDone(g); + }}; + testInvokeOnPool(asyncSingletonPool(), a); + } + + /** Demo from RecursiveAction javadoc */ + static class SortTask extends RecursiveAction { + final long[] array; final int lo, hi; + SortTask(long[] array, int lo, int hi) { + this.array = array; this.lo = lo; this.hi = hi; + } + SortTask(long[] array) { this(array, 0, array.length); } + protected void compute() { + if (hi - lo < THRESHOLD) + sortSequentially(lo, hi); + else { + int mid = (lo + hi) >>> 1; + invokeAll(new SortTask(array, lo, mid), + new SortTask(array, mid, hi)); + merge(lo, mid, hi); + } + } + // implementation details follow: + static final int THRESHOLD = 100; + void sortSequentially(int lo, int hi) { + Arrays.sort(array, lo, hi); + } + void merge(int lo, int mid, int hi) { + long[] buf = Arrays.copyOfRange(array, lo, mid); + for (int i = 0, j = lo, k = mid; i < buf.length; j++) + array[j] = (k == hi || buf[i] < array[k]) ? + buf[i++] : array[k++]; + } + } + + /** + * SortTask demo works as advertised + */ + public void testSortTaskDemo() { + ThreadLocalRandom rnd = ThreadLocalRandom.current(); + long[] array = new long[1007]; + for (int i = 0; i < array.length; i++) + array[i] = rnd.nextLong(); + long[] arrayClone = array.clone(); + testInvokeOnPool(mainPool(), new SortTask(array)); + Arrays.sort(arrayClone); + assertTrue(Arrays.equals(array, arrayClone)); + } +} diff --git a/jdk/test/java/util/concurrent/tck/RecursiveTaskTest.java b/jdk/test/java/util/concurrent/tck/RecursiveTaskTest.java new file mode 100644 index 00000000000..ce13daa8e7b --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/RecursiveTaskTest.java @@ -0,0 +1,1053 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import static java.util.concurrent.TimeUnit.SECONDS; + +import java.util.HashSet; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinTask; +import java.util.concurrent.RecursiveTask; +import java.util.concurrent.TimeoutException; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class RecursiveTaskTest extends JSR166TestCase { + + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(RecursiveTaskTest.class); + } + + private static ForkJoinPool mainPool() { + return new ForkJoinPool(); + } + + private static ForkJoinPool singletonPool() { + return new ForkJoinPool(1); + } + + private static ForkJoinPool asyncSingletonPool() { + return new ForkJoinPool(1, + ForkJoinPool.defaultForkJoinWorkerThreadFactory, + null, true); + } + + private T testInvokeOnPool(ForkJoinPool pool, RecursiveTask a) { + try (PoolCleaner cleaner = cleaner(pool)) { + checkNotDone(a); + + T result = pool.invoke(a); + + checkCompletedNormally(a, result); + return result; + } + } + + void checkNotDone(RecursiveTask a) { + assertFalse(a.isDone()); + assertFalse(a.isCompletedNormally()); + assertFalse(a.isCompletedAbnormally()); + assertFalse(a.isCancelled()); + assertNull(a.getException()); + assertNull(a.getRawResult()); + + if (! ForkJoinTask.inForkJoinPool()) { + Thread.currentThread().interrupt(); + try { + a.get(); + shouldThrow(); + } catch (InterruptedException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + + Thread.currentThread().interrupt(); + try { + a.get(5L, SECONDS); + shouldThrow(); + } catch (InterruptedException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + try { + a.get(0L, SECONDS); + shouldThrow(); + } catch (TimeoutException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + void checkCompletedNormally(RecursiveTask a, T expected) { + assertTrue(a.isDone()); + assertFalse(a.isCancelled()); + assertTrue(a.isCompletedNormally()); + assertFalse(a.isCompletedAbnormally()); + assertNull(a.getException()); + assertSame(expected, a.getRawResult()); + assertSame(expected, a.join()); + assertFalse(a.cancel(false)); + assertFalse(a.cancel(true)); + try { + assertSame(expected, a.get()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + try { + assertSame(expected, a.get(5L, SECONDS)); + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + /** + * Waits for the task to complete, and checks that when it does, + * it will have an Integer result equals to the given int. + */ + void checkCompletesNormally(RecursiveTask a, int expected) { + Integer r = a.join(); + assertEquals(expected, (int) r); + checkCompletedNormally(a, r); + } + + /** + * Like checkCompletesNormally, but verifies that the task has + * already completed. + */ + void checkCompletedNormally(RecursiveTask a, int expected) { + Integer r = a.getRawResult(); + assertEquals(expected, (int) r); + checkCompletedNormally(a, r); + } + + void checkCancelled(RecursiveTask a) { + assertTrue(a.isDone()); + assertTrue(a.isCancelled()); + assertFalse(a.isCompletedNormally()); + assertTrue(a.isCompletedAbnormally()); + assertTrue(a.getException() instanceof CancellationException); + assertNull(a.getRawResult()); + + try { + a.join(); + shouldThrow(); + } catch (CancellationException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + + try { + a.get(); + shouldThrow(); + } catch (CancellationException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + + try { + a.get(5L, SECONDS); + shouldThrow(); + } catch (CancellationException success) { + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + void checkCompletedAbnormally(RecursiveTask a, Throwable t) { + assertTrue(a.isDone()); + assertFalse(a.isCancelled()); + assertFalse(a.isCompletedNormally()); + assertTrue(a.isCompletedAbnormally()); + assertSame(t.getClass(), a.getException().getClass()); + assertNull(a.getRawResult()); + assertFalse(a.cancel(false)); + assertFalse(a.cancel(true)); + + try { + a.join(); + shouldThrow(); + } catch (Throwable expected) { + assertSame(t.getClass(), expected.getClass()); + } + + try { + a.get(); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(t.getClass(), success.getCause().getClass()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + + try { + a.get(5L, SECONDS); + shouldThrow(); + } catch (ExecutionException success) { + assertSame(t.getClass(), success.getCause().getClass()); + } catch (Throwable fail) { threadUnexpectedException(fail); } + } + + public static final class FJException extends RuntimeException { + public FJException() { super(); } + } + + // An invalid return value for Fib + static final Integer NoResult = Integer.valueOf(-17); + + // A simple recursive task for testing + final class FibTask extends CheckedRecursiveTask { + final int number; + FibTask(int n) { number = n; } + public Integer realCompute() { + int n = number; + if (n <= 1) + return n; + FibTask f1 = new FibTask(n - 1); + f1.fork(); + return (new FibTask(n - 2)).compute() + f1.join(); + } + + public void publicSetRawResult(Integer result) { + setRawResult(result); + } + } + + // A recursive action failing in base case + final class FailingFibTask extends RecursiveTask { + final int number; + int result; + FailingFibTask(int n) { number = n; } + public Integer compute() { + int n = number; + if (n <= 1) + throw new FJException(); + FailingFibTask f1 = new FailingFibTask(n - 1); + f1.fork(); + return (new FibTask(n - 2)).compute() + f1.join(); + } + } + + /** + * invoke returns value when task completes normally. + * isCompletedAbnormally and isCancelled return false for normally + * completed tasks. getRawResult of a completed non-null task + * returns value; + */ + public void testInvoke() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask f = new FibTask(8); + Integer r = f.invoke(); + assertEquals(21, (int) r); + checkCompletedNormally(f, r); + return r; + }}; + assertEquals(21, (int) testInvokeOnPool(mainPool(), a)); + } + + /** + * quietlyInvoke task returns when task completes normally. + * isCompletedAbnormally and isCancelled return false for normally + * completed tasks + */ + public void testQuietlyInvoke() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask f = new FibTask(8); + f.quietlyInvoke(); + checkCompletedNormally(f, 21); + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * join of a forked task returns when task completes + */ + public void testForkJoin() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask f = new FibTask(8); + assertSame(f, f.fork()); + Integer r = f.join(); + assertEquals(21, (int) r); + checkCompletedNormally(f, r); + return r; + }}; + assertEquals(21, (int) testInvokeOnPool(mainPool(), a)); + } + + /** + * get of a forked task returns when task completes + */ + public void testForkGet() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() throws Exception { + FibTask f = new FibTask(8); + assertSame(f, f.fork()); + Integer r = f.get(); + assertEquals(21, (int) r); + checkCompletedNormally(f, r); + return r; + }}; + assertEquals(21, (int) testInvokeOnPool(mainPool(), a)); + } + + /** + * timed get of a forked task returns when task completes + */ + public void testForkTimedGet() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() throws Exception { + FibTask f = new FibTask(8); + assertSame(f, f.fork()); + Integer r = f.get(5L, SECONDS); + assertEquals(21, (int) r); + checkCompletedNormally(f, r); + return r; + }}; + assertEquals(21, (int) testInvokeOnPool(mainPool(), a)); + } + + /** + * quietlyJoin of a forked task returns when task completes + */ + public void testForkQuietlyJoin() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask f = new FibTask(8); + assertSame(f, f.fork()); + f.quietlyJoin(); + Integer r = f.getRawResult(); + assertEquals(21, (int) r); + checkCompletedNormally(f, r); + return r; + }}; + assertEquals(21, (int) testInvokeOnPool(mainPool(), a)); + } + + /** + * helpQuiesce returns when tasks are complete. + * getQueuedTaskCount returns 0 when quiescent + */ + public void testForkHelpQuiesce() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask f = new FibTask(8); + assertSame(f, f.fork()); + helpQuiesce(); + while (!f.isDone()) // wait out race + ; + assertEquals(0, getQueuedTaskCount()); + checkCompletedNormally(f, 21); + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * invoke task throws exception when task completes abnormally + */ + public void testAbnormalInvoke() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FailingFibTask f = new FailingFibTask(8); + try { + f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * quietlyInvoke task returns when task completes abnormally + */ + public void testAbnormalQuietlyInvoke() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FailingFibTask f = new FailingFibTask(8); + f.quietlyInvoke(); + assertTrue(f.getException() instanceof FJException); + checkCompletedAbnormally(f, f.getException()); + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * join of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkJoin() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FailingFibTask f = new FailingFibTask(8); + assertSame(f, f.fork()); + try { + Integer r = f.join(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * get of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkGet() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() throws Exception { + FailingFibTask f = new FailingFibTask(8); + assertSame(f, f.fork()); + try { + Integer r = f.get(); + shouldThrow(); + } catch (ExecutionException success) { + Throwable cause = success.getCause(); + assertTrue(cause instanceof FJException); + checkCompletedAbnormally(f, cause); + } + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * timed get of a forked task throws exception when task completes abnormally + */ + public void testAbnormalForkTimedGet() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() throws Exception { + FailingFibTask f = new FailingFibTask(8); + assertSame(f, f.fork()); + try { + Integer r = f.get(5L, SECONDS); + shouldThrow(); + } catch (ExecutionException success) { + Throwable cause = success.getCause(); + assertTrue(cause instanceof FJException); + checkCompletedAbnormally(f, cause); + } + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * quietlyJoin of a forked task returns when task completes abnormally + */ + public void testAbnormalForkQuietlyJoin() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FailingFibTask f = new FailingFibTask(8); + assertSame(f, f.fork()); + f.quietlyJoin(); + assertTrue(f.getException() instanceof FJException); + checkCompletedAbnormally(f, f.getException()); + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * invoke task throws exception when task cancelled + */ + public void testCancelledInvoke() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask f = new FibTask(8); + assertTrue(f.cancel(true)); + try { + Integer r = f.invoke(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * join of a forked task throws exception when task cancelled + */ + public void testCancelledForkJoin() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask f = new FibTask(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + Integer r = f.join(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * get of a forked task throws exception when task cancelled + */ + public void testCancelledForkGet() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() throws Exception { + FibTask f = new FibTask(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + Integer r = f.get(); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * timed get of a forked task throws exception when task cancelled + */ + public void testCancelledForkTimedGet() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() throws Exception { + FibTask f = new FibTask(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + try { + Integer r = f.get(5L, SECONDS); + shouldThrow(); + } catch (CancellationException success) { + checkCancelled(f); + } + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * quietlyJoin of a forked task returns when task cancelled + */ + public void testCancelledForkQuietlyJoin() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask f = new FibTask(8); + assertTrue(f.cancel(true)); + assertSame(f, f.fork()); + f.quietlyJoin(); + checkCancelled(f); + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * getPool of executing task returns its pool + */ + public void testGetPool() { + final ForkJoinPool mainPool = mainPool(); + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + assertSame(mainPool, getPool()); + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool, a)); + } + + /** + * getPool of non-FJ task returns null + */ + public void testGetPool2() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + assertNull(getPool()); + return NoResult; + }}; + assertSame(NoResult, a.invoke()); + } + + /** + * inForkJoinPool of executing task returns true + */ + public void testInForkJoinPool() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + assertTrue(inForkJoinPool()); + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * inForkJoinPool of non-FJ task returns false + */ + public void testInForkJoinPool2() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + assertFalse(inForkJoinPool()); + return NoResult; + }}; + assertSame(NoResult, a.invoke()); + } + + /** + * The value set by setRawResult is returned by getRawResult + */ + public void testSetRawResult() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + setRawResult(NoResult); + assertSame(NoResult, getRawResult()); + return NoResult; + } + }; + assertSame(NoResult, a.invoke()); + } + + /** + * A reinitialized normally completed task may be re-invoked + */ + public void testReinitialize() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask f = new FibTask(8); + checkNotDone(f); + + for (int i = 0; i < 3; i++) { + Integer r = f.invoke(); + assertEquals(21, (int) r); + checkCompletedNormally(f, r); + f.reinitialize(); + f.publicSetRawResult(null); + checkNotDone(f); + } + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * A reinitialized abnormally completed task may be re-invoked + */ + public void testReinitializeAbnormal() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FailingFibTask f = new FailingFibTask(8); + checkNotDone(f); + + for (int i = 0; i < 3; i++) { + try { + f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + f.reinitialize(); + checkNotDone(f); + } + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * invoke task throws exception after invoking completeExceptionally + */ + public void testCompleteExceptionally() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask f = new FibTask(8); + f.completeExceptionally(new FJException()); + try { + Integer r = f.invoke(); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * invoke task suppresses execution invoking complete + */ + public void testComplete() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask f = new FibTask(8); + f.complete(NoResult); + Integer r = f.invoke(); + assertSame(NoResult, r); + checkCompletedNormally(f, NoResult); + return r; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * invokeAll(t1, t2) invokes all task arguments + */ + public void testInvokeAll2() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask f = new FibTask(8); + FibTask g = new FibTask(9); + invokeAll(f, g); + checkCompletedNormally(f, 21); + checkCompletedNormally(g, 34); + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * invokeAll(tasks) with 1 argument invokes task + */ + public void testInvokeAll1() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask f = new FibTask(8); + invokeAll(f); + checkCompletedNormally(f, 21); + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * invokeAll(tasks) with > 2 argument invokes tasks + */ + public void testInvokeAll3() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask f = new FibTask(8); + FibTask g = new FibTask(9); + FibTask h = new FibTask(7); + invokeAll(f, g, h); + assertTrue(f.isDone()); + assertTrue(g.isDone()); + assertTrue(h.isDone()); + checkCompletedNormally(f, 21); + checkCompletedNormally(g, 34); + checkCompletedNormally(h, 13); + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * invokeAll(collection) invokes all tasks in the collection + */ + public void testInvokeAllCollection() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask f = new FibTask(8); + FibTask g = new FibTask(9); + FibTask h = new FibTask(7); + HashSet set = new HashSet(); + set.add(f); + set.add(g); + set.add(h); + invokeAll(set); + assertTrue(f.isDone()); + assertTrue(g.isDone()); + assertTrue(h.isDone()); + checkCompletedNormally(f, 21); + checkCompletedNormally(g, 34); + checkCompletedNormally(h, 13); + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * invokeAll(tasks) with any null task throws NPE + */ + public void testInvokeAllNPE() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask f = new FibTask(8); + FibTask g = new FibTask(9); + FibTask h = null; + try { + invokeAll(f, g, h); + shouldThrow(); + } catch (NullPointerException success) {} + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * invokeAll(t1, t2) throw exception if any task does + */ + public void testAbnormalInvokeAll2() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask f = new FibTask(8); + FailingFibTask g = new FailingFibTask(9); + try { + invokeAll(f, g); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * invokeAll(tasks) with 1 argument throws exception if task does + */ + public void testAbnormalInvokeAll1() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FailingFibTask g = new FailingFibTask(9); + try { + invokeAll(g); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * invokeAll(tasks) with > 2 argument throws exception if any task does + */ + public void testAbnormalInvokeAll3() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask f = new FibTask(8); + FailingFibTask g = new FailingFibTask(9); + FibTask h = new FibTask(7); + try { + invokeAll(f, g, h); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(g, success); + } + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * invokeAll(collection) throws exception if any task does + */ + public void testAbnormalInvokeAllCollection() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FailingFibTask f = new FailingFibTask(8); + FibTask g = new FibTask(9); + FibTask h = new FibTask(7); + HashSet set = new HashSet(); + set.add(f); + set.add(g); + set.add(h); + try { + invokeAll(set); + shouldThrow(); + } catch (FJException success) { + checkCompletedAbnormally(f, success); + } + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(mainPool(), a)); + } + + /** + * tryUnfork returns true for most recent unexecuted task, + * and suppresses execution + */ + public void testTryUnfork() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask g = new FibTask(9); + assertSame(g, g.fork()); + FibTask f = new FibTask(8); + assertSame(f, f.fork()); + assertTrue(f.tryUnfork()); + helpQuiesce(); + checkNotDone(f); + checkCompletedNormally(g, 34); + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(singletonPool(), a)); + } + + /** + * getSurplusQueuedTaskCount returns > 0 when + * there are more tasks than threads + */ + public void testGetSurplusQueuedTaskCount() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask h = new FibTask(7); + assertSame(h, h.fork()); + FibTask g = new FibTask(9); + assertSame(g, g.fork()); + FibTask f = new FibTask(8); + assertSame(f, f.fork()); + assertTrue(getSurplusQueuedTaskCount() > 0); + helpQuiesce(); + assertEquals(0, getSurplusQueuedTaskCount()); + checkCompletedNormally(f, 21); + checkCompletedNormally(g, 34); + checkCompletedNormally(h, 13); + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(singletonPool(), a)); + } + + /** + * peekNextLocalTask returns most recent unexecuted task. + */ + public void testPeekNextLocalTask() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask g = new FibTask(9); + assertSame(g, g.fork()); + FibTask f = new FibTask(8); + assertSame(f, f.fork()); + assertSame(f, peekNextLocalTask()); + checkCompletesNormally(f, 21); + helpQuiesce(); + checkCompletedNormally(g, 34); + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(singletonPool(), a)); + } + + /** + * pollNextLocalTask returns most recent unexecuted task + * without executing it + */ + public void testPollNextLocalTask() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask g = new FibTask(9); + assertSame(g, g.fork()); + FibTask f = new FibTask(8); + assertSame(f, f.fork()); + assertSame(f, pollNextLocalTask()); + helpQuiesce(); + checkNotDone(f); + checkCompletedNormally(g, 34); + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(singletonPool(), a)); + } + + /** + * pollTask returns an unexecuted task without executing it + */ + public void testPollTask() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask g = new FibTask(9); + assertSame(g, g.fork()); + FibTask f = new FibTask(8); + assertSame(f, f.fork()); + assertSame(f, pollTask()); + helpQuiesce(); + checkNotDone(f); + checkCompletedNormally(g, 34); + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(singletonPool(), a)); + } + + /** + * peekNextLocalTask returns least recent unexecuted task in async mode + */ + public void testPeekNextLocalTaskAsync() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask g = new FibTask(9); + assertSame(g, g.fork()); + FibTask f = new FibTask(8); + assertSame(f, f.fork()); + assertSame(g, peekNextLocalTask()); + assertEquals(21, (int) f.join()); + helpQuiesce(); + checkCompletedNormally(f, 21); + checkCompletedNormally(g, 34); + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(asyncSingletonPool(), a)); + } + + /** + * pollNextLocalTask returns least recent unexecuted task without + * executing it, in async mode + */ + public void testPollNextLocalTaskAsync() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask g = new FibTask(9); + assertSame(g, g.fork()); + FibTask f = new FibTask(8); + assertSame(f, f.fork()); + assertSame(g, pollNextLocalTask()); + helpQuiesce(); + checkCompletedNormally(f, 21); + checkNotDone(g); + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(asyncSingletonPool(), a)); + } + + /** + * pollTask returns an unexecuted task without executing it, in + * async mode + */ + public void testPollTaskAsync() { + RecursiveTask a = new CheckedRecursiveTask() { + public Integer realCompute() { + FibTask g = new FibTask(9); + assertSame(g, g.fork()); + FibTask f = new FibTask(8); + assertSame(f, f.fork()); + assertSame(g, pollTask()); + helpQuiesce(); + checkCompletedNormally(f, 21); + checkNotDone(g); + return NoResult; + }}; + assertSame(NoResult, testInvokeOnPool(asyncSingletonPool(), a)); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/ReentrantLockTest.java b/jdk/test/java/util/concurrent/tck/ReentrantLockTest.java new file mode 100644 index 00000000000..8088498562a --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ReentrantLockTest.java @@ -0,0 +1,1163 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ReentrantLockTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(ReentrantLockTest.class); + } + + /** + * A checked runnable calling lockInterruptibly + */ + class InterruptibleLockRunnable extends CheckedRunnable { + final ReentrantLock lock; + InterruptibleLockRunnable(ReentrantLock lock) { this.lock = lock; } + public void realRun() throws InterruptedException { + lock.lockInterruptibly(); + } + } + + /** + * A checked runnable calling lockInterruptibly that expects to be + * interrupted + */ + class InterruptedLockRunnable extends CheckedInterruptedRunnable { + final ReentrantLock lock; + InterruptedLockRunnable(ReentrantLock lock) { this.lock = lock; } + public void realRun() throws InterruptedException { + lock.lockInterruptibly(); + } + } + + /** + * Subclass to expose protected methods + */ + static class PublicReentrantLock extends ReentrantLock { + PublicReentrantLock() { super(); } + PublicReentrantLock(boolean fair) { super(fair); } + public Thread getOwner() { + return super.getOwner(); + } + public Collection getQueuedThreads() { + return super.getQueuedThreads(); + } + public Collection getWaitingThreads(Condition c) { + return super.getWaitingThreads(c); + } + } + + /** + * Releases write lock, checking that it had a hold count of 1. + */ + void releaseLock(PublicReentrantLock lock) { + assertLockedByMoi(lock); + lock.unlock(); + assertFalse(lock.isHeldByCurrentThread()); + assertNotLocked(lock); + } + + /** + * Spin-waits until lock.hasQueuedThread(t) becomes true. + */ + void waitForQueuedThread(PublicReentrantLock lock, Thread t) { + long startTime = System.nanoTime(); + while (!lock.hasQueuedThread(t)) { + if (millisElapsedSince(startTime) > LONG_DELAY_MS) + throw new AssertionFailedError("timed out"); + Thread.yield(); + } + assertTrue(t.isAlive()); + assertNotSame(t, lock.getOwner()); + } + + /** + * Checks that lock is not locked. + */ + void assertNotLocked(PublicReentrantLock lock) { + assertFalse(lock.isLocked()); + assertFalse(lock.isHeldByCurrentThread()); + assertNull(lock.getOwner()); + assertEquals(0, lock.getHoldCount()); + } + + /** + * Checks that lock is locked by the given thread. + */ + void assertLockedBy(PublicReentrantLock lock, Thread t) { + assertTrue(lock.isLocked()); + assertSame(t, lock.getOwner()); + assertEquals(t == Thread.currentThread(), + lock.isHeldByCurrentThread()); + assertEquals(t == Thread.currentThread(), + lock.getHoldCount() > 0); + } + + /** + * Checks that lock is locked by the current thread. + */ + void assertLockedByMoi(PublicReentrantLock lock) { + assertLockedBy(lock, Thread.currentThread()); + } + + /** + * Checks that condition c has no waiters. + */ + void assertHasNoWaiters(PublicReentrantLock lock, Condition c) { + assertHasWaiters(lock, c, new Thread[] {}); + } + + /** + * Checks that condition c has exactly the given waiter threads. + */ + void assertHasWaiters(PublicReentrantLock lock, Condition c, + Thread... threads) { + lock.lock(); + assertEquals(threads.length > 0, lock.hasWaiters(c)); + assertEquals(threads.length, lock.getWaitQueueLength(c)); + assertEquals(threads.length == 0, lock.getWaitingThreads(c).isEmpty()); + assertEquals(threads.length, lock.getWaitingThreads(c).size()); + assertEquals(new HashSet(lock.getWaitingThreads(c)), + new HashSet(Arrays.asList(threads))); + lock.unlock(); + } + + enum AwaitMethod { await, awaitTimed, awaitNanos, awaitUntil } + + /** + * Awaits condition "indefinitely" using the specified AwaitMethod. + */ + void await(Condition c, AwaitMethod awaitMethod) + throws InterruptedException { + long timeoutMillis = 2 * LONG_DELAY_MS; + switch (awaitMethod) { + case await: + c.await(); + break; + case awaitTimed: + assertTrue(c.await(timeoutMillis, MILLISECONDS)); + break; + case awaitNanos: + long timeoutNanos = MILLISECONDS.toNanos(timeoutMillis); + long nanosRemaining = c.awaitNanos(timeoutNanos); + assertTrue(nanosRemaining > timeoutNanos / 2); + assertTrue(nanosRemaining <= timeoutNanos); + break; + case awaitUntil: + assertTrue(c.awaitUntil(delayedDate(timeoutMillis))); + break; + default: + throw new AssertionError(); + } + } + + /** + * Constructor sets given fairness, and is in unlocked state + */ + public void testConstructor() { + PublicReentrantLock lock; + + lock = new PublicReentrantLock(); + assertFalse(lock.isFair()); + assertNotLocked(lock); + + lock = new PublicReentrantLock(true); + assertTrue(lock.isFair()); + assertNotLocked(lock); + + lock = new PublicReentrantLock(false); + assertFalse(lock.isFair()); + assertNotLocked(lock); + } + + /** + * locking an unlocked lock succeeds + */ + public void testLock() { testLock(false); } + public void testLock_fair() { testLock(true); } + public void testLock(boolean fair) { + PublicReentrantLock lock = new PublicReentrantLock(fair); + lock.lock(); + assertLockedByMoi(lock); + releaseLock(lock); + } + + /** + * Unlocking an unlocked lock throws IllegalMonitorStateException + */ + public void testUnlock_IMSE() { testUnlock_IMSE(false); } + public void testUnlock_IMSE_fair() { testUnlock_IMSE(true); } + public void testUnlock_IMSE(boolean fair) { + ReentrantLock lock = new ReentrantLock(fair); + try { + lock.unlock(); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * tryLock on an unlocked lock succeeds + */ + public void testTryLock() { testTryLock(false); } + public void testTryLock_fair() { testTryLock(true); } + public void testTryLock(boolean fair) { + PublicReentrantLock lock = new PublicReentrantLock(fair); + assertTrue(lock.tryLock()); + assertLockedByMoi(lock); + assertTrue(lock.tryLock()); + assertLockedByMoi(lock); + lock.unlock(); + releaseLock(lock); + } + + /** + * hasQueuedThreads reports whether there are waiting threads + */ + public void testHasQueuedThreads() { testHasQueuedThreads(false); } + public void testHasQueuedThreads_fair() { testHasQueuedThreads(true); } + public void testHasQueuedThreads(boolean fair) { + final PublicReentrantLock lock = new PublicReentrantLock(fair); + Thread t1 = new Thread(new InterruptedLockRunnable(lock)); + Thread t2 = new Thread(new InterruptibleLockRunnable(lock)); + assertFalse(lock.hasQueuedThreads()); + lock.lock(); + assertFalse(lock.hasQueuedThreads()); + t1.start(); + waitForQueuedThread(lock, t1); + assertTrue(lock.hasQueuedThreads()); + t2.start(); + waitForQueuedThread(lock, t2); + assertTrue(lock.hasQueuedThreads()); + t1.interrupt(); + awaitTermination(t1); + assertTrue(lock.hasQueuedThreads()); + lock.unlock(); + awaitTermination(t2); + assertFalse(lock.hasQueuedThreads()); + } + + /** + * getQueueLength reports number of waiting threads + */ + public void testGetQueueLength() { testGetQueueLength(false); } + public void testGetQueueLength_fair() { testGetQueueLength(true); } + public void testGetQueueLength(boolean fair) { + final PublicReentrantLock lock = new PublicReentrantLock(fair); + Thread t1 = new Thread(new InterruptedLockRunnable(lock)); + Thread t2 = new Thread(new InterruptibleLockRunnable(lock)); + assertEquals(0, lock.getQueueLength()); + lock.lock(); + t1.start(); + waitForQueuedThread(lock, t1); + assertEquals(1, lock.getQueueLength()); + t2.start(); + waitForQueuedThread(lock, t2); + assertEquals(2, lock.getQueueLength()); + t1.interrupt(); + awaitTermination(t1); + assertEquals(1, lock.getQueueLength()); + lock.unlock(); + awaitTermination(t2); + assertEquals(0, lock.getQueueLength()); + } + + /** + * hasQueuedThread(null) throws NPE + */ + public void testHasQueuedThreadNPE() { testHasQueuedThreadNPE(false); } + public void testHasQueuedThreadNPE_fair() { testHasQueuedThreadNPE(true); } + public void testHasQueuedThreadNPE(boolean fair) { + final ReentrantLock lock = new ReentrantLock(fair); + try { + lock.hasQueuedThread(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * hasQueuedThread reports whether a thread is queued + */ + public void testHasQueuedThread() { testHasQueuedThread(false); } + public void testHasQueuedThread_fair() { testHasQueuedThread(true); } + public void testHasQueuedThread(boolean fair) { + final PublicReentrantLock lock = new PublicReentrantLock(fair); + Thread t1 = new Thread(new InterruptedLockRunnable(lock)); + Thread t2 = new Thread(new InterruptibleLockRunnable(lock)); + assertFalse(lock.hasQueuedThread(t1)); + assertFalse(lock.hasQueuedThread(t2)); + lock.lock(); + t1.start(); + waitForQueuedThread(lock, t1); + assertTrue(lock.hasQueuedThread(t1)); + assertFalse(lock.hasQueuedThread(t2)); + t2.start(); + waitForQueuedThread(lock, t2); + assertTrue(lock.hasQueuedThread(t1)); + assertTrue(lock.hasQueuedThread(t2)); + t1.interrupt(); + awaitTermination(t1); + assertFalse(lock.hasQueuedThread(t1)); + assertTrue(lock.hasQueuedThread(t2)); + lock.unlock(); + awaitTermination(t2); + assertFalse(lock.hasQueuedThread(t1)); + assertFalse(lock.hasQueuedThread(t2)); + } + + /** + * getQueuedThreads includes waiting threads + */ + public void testGetQueuedThreads() { testGetQueuedThreads(false); } + public void testGetQueuedThreads_fair() { testGetQueuedThreads(true); } + public void testGetQueuedThreads(boolean fair) { + final PublicReentrantLock lock = new PublicReentrantLock(fair); + Thread t1 = new Thread(new InterruptedLockRunnable(lock)); + Thread t2 = new Thread(new InterruptibleLockRunnable(lock)); + assertTrue(lock.getQueuedThreads().isEmpty()); + lock.lock(); + assertTrue(lock.getQueuedThreads().isEmpty()); + t1.start(); + waitForQueuedThread(lock, t1); + assertEquals(1, lock.getQueuedThreads().size()); + assertTrue(lock.getQueuedThreads().contains(t1)); + t2.start(); + waitForQueuedThread(lock, t2); + assertEquals(2, lock.getQueuedThreads().size()); + assertTrue(lock.getQueuedThreads().contains(t1)); + assertTrue(lock.getQueuedThreads().contains(t2)); + t1.interrupt(); + awaitTermination(t1); + assertFalse(lock.getQueuedThreads().contains(t1)); + assertTrue(lock.getQueuedThreads().contains(t2)); + assertEquals(1, lock.getQueuedThreads().size()); + lock.unlock(); + awaitTermination(t2); + assertTrue(lock.getQueuedThreads().isEmpty()); + } + + /** + * timed tryLock is interruptible + */ + public void testTryLock_Interruptible() { testTryLock_Interruptible(false); } + public void testTryLock_Interruptible_fair() { testTryLock_Interruptible(true); } + public void testTryLock_Interruptible(boolean fair) { + final PublicReentrantLock lock = new PublicReentrantLock(fair); + lock.lock(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + lock.tryLock(2 * LONG_DELAY_MS, MILLISECONDS); + }}); + + waitForQueuedThread(lock, t); + t.interrupt(); + awaitTermination(t); + releaseLock(lock); + } + + /** + * tryLock on a locked lock fails + */ + public void testTryLockWhenLocked() { testTryLockWhenLocked(false); } + public void testTryLockWhenLocked_fair() { testTryLockWhenLocked(true); } + public void testTryLockWhenLocked(boolean fair) { + final PublicReentrantLock lock = new PublicReentrantLock(fair); + lock.lock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + assertFalse(lock.tryLock()); + }}); + + awaitTermination(t); + releaseLock(lock); + } + + /** + * Timed tryLock on a locked lock times out + */ + public void testTryLock_Timeout() { testTryLock_Timeout(false); } + public void testTryLock_Timeout_fair() { testTryLock_Timeout(true); } + public void testTryLock_Timeout(boolean fair) { + final PublicReentrantLock lock = new PublicReentrantLock(fair); + lock.lock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + long timeoutMillis = 10; + assertFalse(lock.tryLock(timeoutMillis, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + }}); + + awaitTermination(t); + releaseLock(lock); + } + + /** + * getHoldCount returns number of recursive holds + */ + public void testGetHoldCount() { testGetHoldCount(false); } + public void testGetHoldCount_fair() { testGetHoldCount(true); } + public void testGetHoldCount(boolean fair) { + ReentrantLock lock = new ReentrantLock(fair); + for (int i = 1; i <= SIZE; i++) { + lock.lock(); + assertEquals(i, lock.getHoldCount()); + } + for (int i = SIZE; i > 0; i--) { + lock.unlock(); + assertEquals(i - 1, lock.getHoldCount()); + } + } + + /** + * isLocked is true when locked and false when not + */ + public void testIsLocked() { testIsLocked(false); } + public void testIsLocked_fair() { testIsLocked(true); } + public void testIsLocked(boolean fair) { + try { + final ReentrantLock lock = new ReentrantLock(fair); + assertFalse(lock.isLocked()); + lock.lock(); + assertTrue(lock.isLocked()); + lock.lock(); + assertTrue(lock.isLocked()); + lock.unlock(); + assertTrue(lock.isLocked()); + lock.unlock(); + assertFalse(lock.isLocked()); + final CyclicBarrier barrier = new CyclicBarrier(2); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws Exception { + lock.lock(); + assertTrue(lock.isLocked()); + barrier.await(); + barrier.await(); + lock.unlock(); + }}); + + barrier.await(); + assertTrue(lock.isLocked()); + barrier.await(); + awaitTermination(t); + assertFalse(lock.isLocked()); + } catch (Exception fail) { threadUnexpectedException(fail); } + } + + /** + * lockInterruptibly succeeds when unlocked, else is interruptible + */ + public void testLockInterruptibly() { testLockInterruptibly(false); } + public void testLockInterruptibly_fair() { testLockInterruptibly(true); } + public void testLockInterruptibly(boolean fair) { + final PublicReentrantLock lock = new PublicReentrantLock(fair); + try { + lock.lockInterruptibly(); + } catch (InterruptedException fail) { threadUnexpectedException(fail); } + assertLockedByMoi(lock); + Thread t = newStartedThread(new InterruptedLockRunnable(lock)); + waitForQueuedThread(lock, t); + t.interrupt(); + assertTrue(lock.isLocked()); + assertTrue(lock.isHeldByCurrentThread()); + awaitTermination(t); + releaseLock(lock); + } + + /** + * Calling await without holding lock throws IllegalMonitorStateException + */ + public void testAwait_IMSE() { testAwait_IMSE(false); } + public void testAwait_IMSE_fair() { testAwait_IMSE(true); } + public void testAwait_IMSE(boolean fair) { + final ReentrantLock lock = new ReentrantLock(fair); + final Condition c = lock.newCondition(); + for (AwaitMethod awaitMethod : AwaitMethod.values()) { + long startTime = System.nanoTime(); + try { + await(c, awaitMethod); + shouldThrow(); + } catch (IllegalMonitorStateException success) { + } catch (InterruptedException e) { threadUnexpectedException(e); } + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + } + + /** + * Calling signal without holding lock throws IllegalMonitorStateException + */ + public void testSignal_IMSE() { testSignal_IMSE(false); } + public void testSignal_IMSE_fair() { testSignal_IMSE(true); } + public void testSignal_IMSE(boolean fair) { + final ReentrantLock lock = new ReentrantLock(fair); + final Condition c = lock.newCondition(); + try { + c.signal(); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * awaitNanos without a signal times out + */ + public void testAwaitNanos_Timeout() { testAwaitNanos_Timeout(false); } + public void testAwaitNanos_Timeout_fair() { testAwaitNanos_Timeout(true); } + public void testAwaitNanos_Timeout(boolean fair) { + try { + final ReentrantLock lock = new ReentrantLock(fair); + final Condition c = lock.newCondition(); + lock.lock(); + long startTime = System.nanoTime(); + long timeoutMillis = 10; + long timeoutNanos = MILLISECONDS.toNanos(timeoutMillis); + long nanosRemaining = c.awaitNanos(timeoutNanos); + assertTrue(nanosRemaining <= 0); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + lock.unlock(); + } catch (InterruptedException fail) { threadUnexpectedException(fail); } + } + + /** + * timed await without a signal times out + */ + public void testAwait_Timeout() { testAwait_Timeout(false); } + public void testAwait_Timeout_fair() { testAwait_Timeout(true); } + public void testAwait_Timeout(boolean fair) { + try { + final ReentrantLock lock = new ReentrantLock(fair); + final Condition c = lock.newCondition(); + lock.lock(); + long startTime = System.nanoTime(); + long timeoutMillis = 10; + assertFalse(c.await(timeoutMillis, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + lock.unlock(); + } catch (InterruptedException fail) { threadUnexpectedException(fail); } + } + + /** + * awaitUntil without a signal times out + */ + public void testAwaitUntil_Timeout() { testAwaitUntil_Timeout(false); } + public void testAwaitUntil_Timeout_fair() { testAwaitUntil_Timeout(true); } + public void testAwaitUntil_Timeout(boolean fair) { + try { + final ReentrantLock lock = new ReentrantLock(fair); + final Condition c = lock.newCondition(); + lock.lock(); + // We shouldn't assume that nanoTime and currentTimeMillis + // use the same time source, so don't use nanoTime here. + java.util.Date delayedDate = delayedDate(timeoutMillis()); + assertFalse(c.awaitUntil(delayedDate)); + assertTrue(new java.util.Date().getTime() >= delayedDate.getTime()); + lock.unlock(); + } catch (InterruptedException fail) { threadUnexpectedException(fail); } + } + + /** + * await returns when signalled + */ + public void testAwait() { testAwait(false); } + public void testAwait_fair() { testAwait(true); } + public void testAwait(boolean fair) { + final PublicReentrantLock lock = new PublicReentrantLock(fair); + final Condition c = lock.newCondition(); + final CountDownLatch locked = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + lock.lock(); + locked.countDown(); + c.await(); + lock.unlock(); + }}); + + await(locked); + lock.lock(); + assertHasWaiters(lock, c, t); + c.signal(); + assertHasNoWaiters(lock, c); + assertTrue(t.isAlive()); + lock.unlock(); + awaitTermination(t); + } + + /** + * hasWaiters throws NPE if null + */ + public void testHasWaitersNPE() { testHasWaitersNPE(false); } + public void testHasWaitersNPE_fair() { testHasWaitersNPE(true); } + public void testHasWaitersNPE(boolean fair) { + final ReentrantLock lock = new ReentrantLock(fair); + try { + lock.hasWaiters(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * getWaitQueueLength throws NPE if null + */ + public void testGetWaitQueueLengthNPE() { testGetWaitQueueLengthNPE(false); } + public void testGetWaitQueueLengthNPE_fair() { testGetWaitQueueLengthNPE(true); } + public void testGetWaitQueueLengthNPE(boolean fair) { + final ReentrantLock lock = new ReentrantLock(fair); + try { + lock.getWaitQueueLength(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * getWaitingThreads throws NPE if null + */ + public void testGetWaitingThreadsNPE() { testGetWaitingThreadsNPE(false); } + public void testGetWaitingThreadsNPE_fair() { testGetWaitingThreadsNPE(true); } + public void testGetWaitingThreadsNPE(boolean fair) { + final PublicReentrantLock lock = new PublicReentrantLock(fair); + try { + lock.getWaitingThreads(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * hasWaiters throws IllegalArgumentException if not owned + */ + public void testHasWaitersIAE() { testHasWaitersIAE(false); } + public void testHasWaitersIAE_fair() { testHasWaitersIAE(true); } + public void testHasWaitersIAE(boolean fair) { + final ReentrantLock lock = new ReentrantLock(fair); + final Condition c = lock.newCondition(); + final ReentrantLock lock2 = new ReentrantLock(fair); + try { + lock2.hasWaiters(c); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * hasWaiters throws IllegalMonitorStateException if not locked + */ + public void testHasWaitersIMSE() { testHasWaitersIMSE(false); } + public void testHasWaitersIMSE_fair() { testHasWaitersIMSE(true); } + public void testHasWaitersIMSE(boolean fair) { + final ReentrantLock lock = new ReentrantLock(fair); + final Condition c = lock.newCondition(); + try { + lock.hasWaiters(c); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * getWaitQueueLength throws IllegalArgumentException if not owned + */ + public void testGetWaitQueueLengthIAE() { testGetWaitQueueLengthIAE(false); } + public void testGetWaitQueueLengthIAE_fair() { testGetWaitQueueLengthIAE(true); } + public void testGetWaitQueueLengthIAE(boolean fair) { + final ReentrantLock lock = new ReentrantLock(fair); + final Condition c = lock.newCondition(); + final ReentrantLock lock2 = new ReentrantLock(fair); + try { + lock2.getWaitQueueLength(c); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * getWaitQueueLength throws IllegalMonitorStateException if not locked + */ + public void testGetWaitQueueLengthIMSE() { testGetWaitQueueLengthIMSE(false); } + public void testGetWaitQueueLengthIMSE_fair() { testGetWaitQueueLengthIMSE(true); } + public void testGetWaitQueueLengthIMSE(boolean fair) { + final ReentrantLock lock = new ReentrantLock(fair); + final Condition c = lock.newCondition(); + try { + lock.getWaitQueueLength(c); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * getWaitingThreads throws IllegalArgumentException if not owned + */ + public void testGetWaitingThreadsIAE() { testGetWaitingThreadsIAE(false); } + public void testGetWaitingThreadsIAE_fair() { testGetWaitingThreadsIAE(true); } + public void testGetWaitingThreadsIAE(boolean fair) { + final PublicReentrantLock lock = new PublicReentrantLock(fair); + final Condition c = lock.newCondition(); + final PublicReentrantLock lock2 = new PublicReentrantLock(fair); + try { + lock2.getWaitingThreads(c); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * getWaitingThreads throws IllegalMonitorStateException if not locked + */ + public void testGetWaitingThreadsIMSE() { testGetWaitingThreadsIMSE(false); } + public void testGetWaitingThreadsIMSE_fair() { testGetWaitingThreadsIMSE(true); } + public void testGetWaitingThreadsIMSE(boolean fair) { + final PublicReentrantLock lock = new PublicReentrantLock(fair); + final Condition c = lock.newCondition(); + try { + lock.getWaitingThreads(c); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * hasWaiters returns true when a thread is waiting, else false + */ + public void testHasWaiters() { testHasWaiters(false); } + public void testHasWaiters_fair() { testHasWaiters(true); } + public void testHasWaiters(boolean fair) { + final PublicReentrantLock lock = new PublicReentrantLock(fair); + final Condition c = lock.newCondition(); + final CountDownLatch pleaseSignal = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + lock.lock(); + assertHasNoWaiters(lock, c); + assertFalse(lock.hasWaiters(c)); + pleaseSignal.countDown(); + c.await(); + assertHasNoWaiters(lock, c); + assertFalse(lock.hasWaiters(c)); + lock.unlock(); + }}); + + await(pleaseSignal); + lock.lock(); + assertHasWaiters(lock, c, t); + assertTrue(lock.hasWaiters(c)); + c.signal(); + assertHasNoWaiters(lock, c); + assertFalse(lock.hasWaiters(c)); + lock.unlock(); + awaitTermination(t); + assertHasNoWaiters(lock, c); + } + + /** + * getWaitQueueLength returns number of waiting threads + */ + public void testGetWaitQueueLength() { testGetWaitQueueLength(false); } + public void testGetWaitQueueLength_fair() { testGetWaitQueueLength(true); } + public void testGetWaitQueueLength(boolean fair) { + final PublicReentrantLock lock = new PublicReentrantLock(fair); + final Condition c = lock.newCondition(); + final CountDownLatch locked1 = new CountDownLatch(1); + final CountDownLatch locked2 = new CountDownLatch(1); + Thread t1 = new Thread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + lock.lock(); + assertFalse(lock.hasWaiters(c)); + assertEquals(0, lock.getWaitQueueLength(c)); + locked1.countDown(); + c.await(); + lock.unlock(); + }}); + + Thread t2 = new Thread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + lock.lock(); + assertTrue(lock.hasWaiters(c)); + assertEquals(1, lock.getWaitQueueLength(c)); + locked2.countDown(); + c.await(); + lock.unlock(); + }}); + + lock.lock(); + assertEquals(0, lock.getWaitQueueLength(c)); + lock.unlock(); + + t1.start(); + await(locked1); + + lock.lock(); + assertHasWaiters(lock, c, t1); + assertEquals(1, lock.getWaitQueueLength(c)); + lock.unlock(); + + t2.start(); + await(locked2); + + lock.lock(); + assertHasWaiters(lock, c, t1, t2); + assertEquals(2, lock.getWaitQueueLength(c)); + c.signalAll(); + assertHasNoWaiters(lock, c); + lock.unlock(); + + awaitTermination(t1); + awaitTermination(t2); + + assertHasNoWaiters(lock, c); + } + + /** + * getWaitingThreads returns only and all waiting threads + */ + public void testGetWaitingThreads() { testGetWaitingThreads(false); } + public void testGetWaitingThreads_fair() { testGetWaitingThreads(true); } + public void testGetWaitingThreads(boolean fair) { + final PublicReentrantLock lock = new PublicReentrantLock(fair); + final Condition c = lock.newCondition(); + final CountDownLatch locked1 = new CountDownLatch(1); + final CountDownLatch locked2 = new CountDownLatch(1); + Thread t1 = new Thread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + lock.lock(); + assertTrue(lock.getWaitingThreads(c).isEmpty()); + locked1.countDown(); + c.await(); + lock.unlock(); + }}); + + Thread t2 = new Thread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + lock.lock(); + assertFalse(lock.getWaitingThreads(c).isEmpty()); + locked2.countDown(); + c.await(); + lock.unlock(); + }}); + + lock.lock(); + assertTrue(lock.getWaitingThreads(c).isEmpty()); + lock.unlock(); + + t1.start(); + await(locked1); + + lock.lock(); + assertHasWaiters(lock, c, t1); + assertTrue(lock.getWaitingThreads(c).contains(t1)); + assertFalse(lock.getWaitingThreads(c).contains(t2)); + assertEquals(1, lock.getWaitingThreads(c).size()); + lock.unlock(); + + t2.start(); + await(locked2); + + lock.lock(); + assertHasWaiters(lock, c, t1, t2); + assertTrue(lock.getWaitingThreads(c).contains(t1)); + assertTrue(lock.getWaitingThreads(c).contains(t2)); + assertEquals(2, lock.getWaitingThreads(c).size()); + c.signalAll(); + assertHasNoWaiters(lock, c); + lock.unlock(); + + awaitTermination(t1); + awaitTermination(t2); + + assertHasNoWaiters(lock, c); + } + + /** + * awaitUninterruptibly is uninterruptible + */ + public void testAwaitUninterruptibly() { testAwaitUninterruptibly(false); } + public void testAwaitUninterruptibly_fair() { testAwaitUninterruptibly(true); } + public void testAwaitUninterruptibly(boolean fair) { + final ReentrantLock lock = new ReentrantLock(fair); + final Condition c = lock.newCondition(); + final CountDownLatch pleaseInterrupt = new CountDownLatch(2); + + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() { + // Interrupt before awaitUninterruptibly + lock.lock(); + pleaseInterrupt.countDown(); + Thread.currentThread().interrupt(); + c.awaitUninterruptibly(); + assertTrue(Thread.interrupted()); + lock.unlock(); + }}); + + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() { + // Interrupt during awaitUninterruptibly + lock.lock(); + pleaseInterrupt.countDown(); + c.awaitUninterruptibly(); + assertTrue(Thread.interrupted()); + lock.unlock(); + }}); + + await(pleaseInterrupt); + lock.lock(); + lock.unlock(); + t2.interrupt(); + + assertThreadStaysAlive(t1); + assertTrue(t2.isAlive()); + + lock.lock(); + c.signalAll(); + lock.unlock(); + + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * await/awaitNanos/awaitUntil is interruptible + */ + public void testInterruptible_await() { testInterruptible(false, AwaitMethod.await); } + public void testInterruptible_await_fair() { testInterruptible(true, AwaitMethod.await); } + public void testInterruptible_awaitTimed() { testInterruptible(false, AwaitMethod.awaitTimed); } + public void testInterruptible_awaitTimed_fair() { testInterruptible(true, AwaitMethod.awaitTimed); } + public void testInterruptible_awaitNanos() { testInterruptible(false, AwaitMethod.awaitNanos); } + public void testInterruptible_awaitNanos_fair() { testInterruptible(true, AwaitMethod.awaitNanos); } + public void testInterruptible_awaitUntil() { testInterruptible(false, AwaitMethod.awaitUntil); } + public void testInterruptible_awaitUntil_fair() { testInterruptible(true, AwaitMethod.awaitUntil); } + public void testInterruptible(boolean fair, final AwaitMethod awaitMethod) { + final PublicReentrantLock lock = + new PublicReentrantLock(fair); + final Condition c = lock.newCondition(); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + lock.lock(); + assertLockedByMoi(lock); + assertHasNoWaiters(lock, c); + pleaseInterrupt.countDown(); + try { + await(c, awaitMethod); + } finally { + assertLockedByMoi(lock); + assertHasNoWaiters(lock, c); + lock.unlock(); + assertFalse(Thread.interrupted()); + } + }}); + + await(pleaseInterrupt); + assertHasWaiters(lock, c, t); + t.interrupt(); + awaitTermination(t); + assertNotLocked(lock); + } + + /** + * signalAll wakes up all threads + */ + public void testSignalAll_await() { testSignalAll(false, AwaitMethod.await); } + public void testSignalAll_await_fair() { testSignalAll(true, AwaitMethod.await); } + public void testSignalAll_awaitTimed() { testSignalAll(false, AwaitMethod.awaitTimed); } + public void testSignalAll_awaitTimed_fair() { testSignalAll(true, AwaitMethod.awaitTimed); } + public void testSignalAll_awaitNanos() { testSignalAll(false, AwaitMethod.awaitNanos); } + public void testSignalAll_awaitNanos_fair() { testSignalAll(true, AwaitMethod.awaitNanos); } + public void testSignalAll_awaitUntil() { testSignalAll(false, AwaitMethod.awaitUntil); } + public void testSignalAll_awaitUntil_fair() { testSignalAll(true, AwaitMethod.awaitUntil); } + public void testSignalAll(boolean fair, final AwaitMethod awaitMethod) { + final PublicReentrantLock lock = new PublicReentrantLock(fair); + final Condition c = lock.newCondition(); + final CountDownLatch pleaseSignal = new CountDownLatch(2); + class Awaiter extends CheckedRunnable { + public void realRun() throws InterruptedException { + lock.lock(); + pleaseSignal.countDown(); + await(c, awaitMethod); + lock.unlock(); + } + } + + Thread t1 = newStartedThread(new Awaiter()); + Thread t2 = newStartedThread(new Awaiter()); + + await(pleaseSignal); + lock.lock(); + assertHasWaiters(lock, c, t1, t2); + c.signalAll(); + assertHasNoWaiters(lock, c); + lock.unlock(); + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * signal wakes up waiting threads in FIFO order + */ + public void testSignalWakesFifo() { testSignalWakesFifo(false); } + public void testSignalWakesFifo_fair() { testSignalWakesFifo(true); } + public void testSignalWakesFifo(boolean fair) { + final PublicReentrantLock lock = + new PublicReentrantLock(fair); + final Condition c = lock.newCondition(); + final CountDownLatch locked1 = new CountDownLatch(1); + final CountDownLatch locked2 = new CountDownLatch(1); + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + lock.lock(); + locked1.countDown(); + c.await(); + lock.unlock(); + }}); + + await(locked1); + + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + lock.lock(); + locked2.countDown(); + c.await(); + lock.unlock(); + }}); + + await(locked2); + + lock.lock(); + assertHasWaiters(lock, c, t1, t2); + assertFalse(lock.hasQueuedThreads()); + c.signal(); + assertHasWaiters(lock, c, t2); + assertTrue(lock.hasQueuedThread(t1)); + assertFalse(lock.hasQueuedThread(t2)); + c.signal(); + assertHasNoWaiters(lock, c); + assertTrue(lock.hasQueuedThread(t1)); + assertTrue(lock.hasQueuedThread(t2)); + lock.unlock(); + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * await after multiple reentrant locking preserves lock count + */ + public void testAwaitLockCount() { testAwaitLockCount(false); } + public void testAwaitLockCount_fair() { testAwaitLockCount(true); } + public void testAwaitLockCount(boolean fair) { + final PublicReentrantLock lock = new PublicReentrantLock(fair); + final Condition c = lock.newCondition(); + final CountDownLatch pleaseSignal = new CountDownLatch(2); + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + lock.lock(); + assertLockedByMoi(lock); + assertEquals(1, lock.getHoldCount()); + pleaseSignal.countDown(); + c.await(); + assertLockedByMoi(lock); + assertEquals(1, lock.getHoldCount()); + lock.unlock(); + }}); + + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + lock.lock(); + lock.lock(); + assertLockedByMoi(lock); + assertEquals(2, lock.getHoldCount()); + pleaseSignal.countDown(); + c.await(); + assertLockedByMoi(lock); + assertEquals(2, lock.getHoldCount()); + lock.unlock(); + lock.unlock(); + }}); + + await(pleaseSignal); + lock.lock(); + assertHasWaiters(lock, c, t1, t2); + assertEquals(1, lock.getHoldCount()); + c.signalAll(); + assertHasNoWaiters(lock, c); + lock.unlock(); + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * A serialized lock deserializes as unlocked + */ + public void testSerialization() { testSerialization(false); } + public void testSerialization_fair() { testSerialization(true); } + public void testSerialization(boolean fair) { + ReentrantLock lock = new ReentrantLock(fair); + lock.lock(); + + ReentrantLock clone = serialClone(lock); + assertEquals(lock.isFair(), clone.isFair()); + assertTrue(lock.isLocked()); + assertFalse(clone.isLocked()); + assertEquals(1, lock.getHoldCount()); + assertEquals(0, clone.getHoldCount()); + clone.lock(); + clone.lock(); + assertTrue(clone.isLocked()); + assertEquals(2, clone.getHoldCount()); + assertEquals(1, lock.getHoldCount()); + clone.unlock(); + clone.unlock(); + assertTrue(lock.isLocked()); + assertFalse(clone.isLocked()); + } + + /** + * toString indicates current lock state + */ + public void testToString() { testToString(false); } + public void testToString_fair() { testToString(true); } + public void testToString(boolean fair) { + ReentrantLock lock = new ReentrantLock(fair); + assertTrue(lock.toString().contains("Unlocked")); + lock.lock(); + assertTrue(lock.toString().contains("Locked")); + lock.unlock(); + assertTrue(lock.toString().contains("Unlocked")); + } +} diff --git a/jdk/test/java/util/concurrent/tck/ReentrantReadWriteLockTest.java b/jdk/test/java/util/concurrent/tck/ReentrantReadWriteLockTest.java new file mode 100644 index 00000000000..a2e597e5eb7 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ReentrantReadWriteLockTest.java @@ -0,0 +1,1703 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ReentrantReadWriteLockTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(ReentrantReadWriteLockTest.class); + } + + /** + * A runnable calling lockInterruptibly + */ + class InterruptibleLockRunnable extends CheckedRunnable { + final ReentrantReadWriteLock lock; + InterruptibleLockRunnable(ReentrantReadWriteLock l) { lock = l; } + public void realRun() throws InterruptedException { + lock.writeLock().lockInterruptibly(); + } + } + + /** + * A runnable calling lockInterruptibly that expects to be + * interrupted + */ + class InterruptedLockRunnable extends CheckedInterruptedRunnable { + final ReentrantReadWriteLock lock; + InterruptedLockRunnable(ReentrantReadWriteLock l) { lock = l; } + public void realRun() throws InterruptedException { + lock.writeLock().lockInterruptibly(); + } + } + + /** + * Subclass to expose protected methods + */ + static class PublicReentrantReadWriteLock extends ReentrantReadWriteLock { + PublicReentrantReadWriteLock() { super(); } + PublicReentrantReadWriteLock(boolean fair) { super(fair); } + public Thread getOwner() { + return super.getOwner(); + } + public Collection getQueuedThreads() { + return super.getQueuedThreads(); + } + public Collection getWaitingThreads(Condition c) { + return super.getWaitingThreads(c); + } + } + + /** + * Releases write lock, checking that it had a hold count of 1. + */ + void releaseWriteLock(PublicReentrantReadWriteLock lock) { + ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock(); + assertWriteLockedByMoi(lock); + assertEquals(1, lock.getWriteHoldCount()); + writeLock.unlock(); + assertNotWriteLocked(lock); + } + + /** + * Spin-waits until lock.hasQueuedThread(t) becomes true. + */ + void waitForQueuedThread(PublicReentrantReadWriteLock lock, Thread t) { + long startTime = System.nanoTime(); + while (!lock.hasQueuedThread(t)) { + if (millisElapsedSince(startTime) > LONG_DELAY_MS) + throw new AssertionFailedError("timed out"); + Thread.yield(); + } + assertTrue(t.isAlive()); + assertNotSame(t, lock.getOwner()); + } + + /** + * Checks that lock is not write-locked. + */ + void assertNotWriteLocked(PublicReentrantReadWriteLock lock) { + assertFalse(lock.isWriteLocked()); + assertFalse(lock.isWriteLockedByCurrentThread()); + assertFalse(lock.writeLock().isHeldByCurrentThread()); + assertEquals(0, lock.getWriteHoldCount()); + assertEquals(0, lock.writeLock().getHoldCount()); + assertNull(lock.getOwner()); + } + + /** + * Checks that lock is write-locked by the given thread. + */ + void assertWriteLockedBy(PublicReentrantReadWriteLock lock, Thread t) { + assertTrue(lock.isWriteLocked()); + assertSame(t, lock.getOwner()); + assertEquals(t == Thread.currentThread(), + lock.isWriteLockedByCurrentThread()); + assertEquals(t == Thread.currentThread(), + lock.writeLock().isHeldByCurrentThread()); + assertEquals(t == Thread.currentThread(), + lock.getWriteHoldCount() > 0); + assertEquals(t == Thread.currentThread(), + lock.writeLock().getHoldCount() > 0); + assertEquals(0, lock.getReadLockCount()); + } + + /** + * Checks that lock is write-locked by the current thread. + */ + void assertWriteLockedByMoi(PublicReentrantReadWriteLock lock) { + assertWriteLockedBy(lock, Thread.currentThread()); + } + + /** + * Checks that condition c has no waiters. + */ + void assertHasNoWaiters(PublicReentrantReadWriteLock lock, Condition c) { + assertHasWaiters(lock, c, new Thread[] {}); + } + + /** + * Checks that condition c has exactly the given waiter threads. + */ + void assertHasWaiters(PublicReentrantReadWriteLock lock, Condition c, + Thread... threads) { + lock.writeLock().lock(); + assertEquals(threads.length > 0, lock.hasWaiters(c)); + assertEquals(threads.length, lock.getWaitQueueLength(c)); + assertEquals(threads.length == 0, lock.getWaitingThreads(c).isEmpty()); + assertEquals(threads.length, lock.getWaitingThreads(c).size()); + assertEquals(new HashSet(lock.getWaitingThreads(c)), + new HashSet(Arrays.asList(threads))); + lock.writeLock().unlock(); + } + + enum AwaitMethod { await, awaitTimed, awaitNanos, awaitUntil } + + /** + * Awaits condition "indefinitely" using the specified AwaitMethod. + */ + void await(Condition c, AwaitMethod awaitMethod) + throws InterruptedException { + long timeoutMillis = 2 * LONG_DELAY_MS; + switch (awaitMethod) { + case await: + c.await(); + break; + case awaitTimed: + assertTrue(c.await(timeoutMillis, MILLISECONDS)); + break; + case awaitNanos: + long timeoutNanos = MILLISECONDS.toNanos(timeoutMillis); + long nanosRemaining = c.awaitNanos(timeoutNanos); + assertTrue(nanosRemaining > timeoutNanos / 2); + assertTrue(nanosRemaining <= timeoutNanos); + break; + case awaitUntil: + assertTrue(c.awaitUntil(delayedDate(timeoutMillis))); + break; + default: + throw new AssertionError(); + } + } + + /** + * Constructor sets given fairness, and is in unlocked state + */ + public void testConstructor() { + PublicReentrantReadWriteLock lock; + + lock = new PublicReentrantReadWriteLock(); + assertFalse(lock.isFair()); + assertNotWriteLocked(lock); + assertEquals(0, lock.getReadLockCount()); + + lock = new PublicReentrantReadWriteLock(true); + assertTrue(lock.isFair()); + assertNotWriteLocked(lock); + assertEquals(0, lock.getReadLockCount()); + + lock = new PublicReentrantReadWriteLock(false); + assertFalse(lock.isFair()); + assertNotWriteLocked(lock); + assertEquals(0, lock.getReadLockCount()); + } + + /** + * write-locking and read-locking an unlocked lock succeed + */ + public void testLock() { testLock(false); } + public void testLock_fair() { testLock(true); } + public void testLock(boolean fair) { + PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + assertNotWriteLocked(lock); + lock.writeLock().lock(); + assertWriteLockedByMoi(lock); + lock.writeLock().unlock(); + assertNotWriteLocked(lock); + assertEquals(0, lock.getReadLockCount()); + lock.readLock().lock(); + assertNotWriteLocked(lock); + assertEquals(1, lock.getReadLockCount()); + lock.readLock().unlock(); + assertNotWriteLocked(lock); + assertEquals(0, lock.getReadLockCount()); + } + + /** + * getWriteHoldCount returns number of recursive holds + */ + public void testGetWriteHoldCount() { testGetWriteHoldCount(false); } + public void testGetWriteHoldCount_fair() { testGetWriteHoldCount(true); } + public void testGetWriteHoldCount(boolean fair) { + ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair); + for (int i = 1; i <= SIZE; i++) { + lock.writeLock().lock(); + assertEquals(i,lock.getWriteHoldCount()); + } + for (int i = SIZE; i > 0; i--) { + lock.writeLock().unlock(); + assertEquals(i - 1,lock.getWriteHoldCount()); + } + } + + /** + * writelock.getHoldCount returns number of recursive holds + */ + public void testGetHoldCount() { testGetHoldCount(false); } + public void testGetHoldCount_fair() { testGetHoldCount(true); } + public void testGetHoldCount(boolean fair) { + ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair); + for (int i = 1; i <= SIZE; i++) { + lock.writeLock().lock(); + assertEquals(i,lock.writeLock().getHoldCount()); + } + for (int i = SIZE; i > 0; i--) { + lock.writeLock().unlock(); + assertEquals(i - 1,lock.writeLock().getHoldCount()); + } + } + + /** + * getReadHoldCount returns number of recursive holds + */ + public void testGetReadHoldCount() { testGetReadHoldCount(false); } + public void testGetReadHoldCount_fair() { testGetReadHoldCount(true); } + public void testGetReadHoldCount(boolean fair) { + ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair); + for (int i = 1; i <= SIZE; i++) { + lock.readLock().lock(); + assertEquals(i,lock.getReadHoldCount()); + } + for (int i = SIZE; i > 0; i--) { + lock.readLock().unlock(); + assertEquals(i - 1,lock.getReadHoldCount()); + } + } + + /** + * write-unlocking an unlocked lock throws IllegalMonitorStateException + */ + public void testWriteUnlock_IMSE() { testWriteUnlock_IMSE(false); } + public void testWriteUnlock_IMSE_fair() { testWriteUnlock_IMSE(true); } + public void testWriteUnlock_IMSE(boolean fair) { + ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair); + try { + lock.writeLock().unlock(); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * read-unlocking an unlocked lock throws IllegalMonitorStateException + */ + public void testReadUnlock_IMSE() { testReadUnlock_IMSE(false); } + public void testReadUnlock_IMSE_fair() { testReadUnlock_IMSE(true); } + public void testReadUnlock_IMSE(boolean fair) { + ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair); + try { + lock.readLock().unlock(); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * write-lockInterruptibly is interruptible + */ + public void testWriteLockInterruptibly_Interruptible() { testWriteLockInterruptibly_Interruptible(false); } + public void testWriteLockInterruptibly_Interruptible_fair() { testWriteLockInterruptibly_Interruptible(true); } + public void testWriteLockInterruptibly_Interruptible(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + lock.writeLock().lock(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + lock.writeLock().lockInterruptibly(); + }}); + + waitForQueuedThread(lock, t); + t.interrupt(); + awaitTermination(t); + releaseWriteLock(lock); + } + + /** + * timed write-tryLock is interruptible + */ + public void testWriteTryLock_Interruptible() { testWriteTryLock_Interruptible(false); } + public void testWriteTryLock_Interruptible_fair() { testWriteTryLock_Interruptible(true); } + public void testWriteTryLock_Interruptible(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + lock.writeLock().lock(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + lock.writeLock().tryLock(2 * LONG_DELAY_MS, MILLISECONDS); + }}); + + waitForQueuedThread(lock, t); + t.interrupt(); + awaitTermination(t); + releaseWriteLock(lock); + } + + /** + * read-lockInterruptibly is interruptible + */ + public void testReadLockInterruptibly_Interruptible() { testReadLockInterruptibly_Interruptible(false); } + public void testReadLockInterruptibly_Interruptible_fair() { testReadLockInterruptibly_Interruptible(true); } + public void testReadLockInterruptibly_Interruptible(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + lock.writeLock().lock(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + lock.readLock().lockInterruptibly(); + }}); + + waitForQueuedThread(lock, t); + t.interrupt(); + awaitTermination(t); + releaseWriteLock(lock); + } + + /** + * timed read-tryLock is interruptible + */ + public void testReadTryLock_Interruptible() { testReadTryLock_Interruptible(false); } + public void testReadTryLock_Interruptible_fair() { testReadTryLock_Interruptible(true); } + public void testReadTryLock_Interruptible(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + lock.writeLock().lock(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + lock.readLock().tryLock(2 * LONG_DELAY_MS, MILLISECONDS); + }}); + + waitForQueuedThread(lock, t); + t.interrupt(); + awaitTermination(t); + releaseWriteLock(lock); + } + + /** + * write-tryLock on an unlocked lock succeeds + */ + public void testWriteTryLock() { testWriteTryLock(false); } + public void testWriteTryLock_fair() { testWriteTryLock(true); } + public void testWriteTryLock(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + assertTrue(lock.writeLock().tryLock()); + assertWriteLockedByMoi(lock); + assertTrue(lock.writeLock().tryLock()); + assertWriteLockedByMoi(lock); + lock.writeLock().unlock(); + releaseWriteLock(lock); + } + + /** + * write-tryLock fails if locked + */ + public void testWriteTryLockWhenLocked() { testWriteTryLockWhenLocked(false); } + public void testWriteTryLockWhenLocked_fair() { testWriteTryLockWhenLocked(true); } + public void testWriteTryLockWhenLocked(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + lock.writeLock().lock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + assertFalse(lock.writeLock().tryLock()); + }}); + + awaitTermination(t); + releaseWriteLock(lock); + } + + /** + * read-tryLock fails if locked + */ + public void testReadTryLockWhenLocked() { testReadTryLockWhenLocked(false); } + public void testReadTryLockWhenLocked_fair() { testReadTryLockWhenLocked(true); } + public void testReadTryLockWhenLocked(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + lock.writeLock().lock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + assertFalse(lock.readLock().tryLock()); + }}); + + awaitTermination(t); + releaseWriteLock(lock); + } + + /** + * Multiple threads can hold a read lock when not write-locked + */ + public void testMultipleReadLocks() { testMultipleReadLocks(false); } + public void testMultipleReadLocks_fair() { testMultipleReadLocks(true); } + public void testMultipleReadLocks(boolean fair) { + final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair); + lock.readLock().lock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertTrue(lock.readLock().tryLock()); + lock.readLock().unlock(); + assertTrue(lock.readLock().tryLock(LONG_DELAY_MS, MILLISECONDS)); + lock.readLock().unlock(); + lock.readLock().lock(); + lock.readLock().unlock(); + }}); + + awaitTermination(t); + lock.readLock().unlock(); + } + + /** + * A writelock succeeds only after a reading thread unlocks + */ + public void testWriteAfterReadLock() { testWriteAfterReadLock(false); } + public void testWriteAfterReadLock_fair() { testWriteAfterReadLock(true); } + public void testWriteAfterReadLock(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + lock.readLock().lock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + assertEquals(1, lock.getReadLockCount()); + lock.writeLock().lock(); + assertEquals(0, lock.getReadLockCount()); + lock.writeLock().unlock(); + }}); + waitForQueuedThread(lock, t); + assertNotWriteLocked(lock); + assertEquals(1, lock.getReadLockCount()); + lock.readLock().unlock(); + assertEquals(0, lock.getReadLockCount()); + awaitTermination(t); + assertNotWriteLocked(lock); + } + + /** + * A writelock succeeds only after reading threads unlock + */ + public void testWriteAfterMultipleReadLocks() { testWriteAfterMultipleReadLocks(false); } + public void testWriteAfterMultipleReadLocks_fair() { testWriteAfterMultipleReadLocks(true); } + public void testWriteAfterMultipleReadLocks(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + lock.readLock().lock(); + lock.readLock().lock(); + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() { + lock.readLock().lock(); + assertEquals(3, lock.getReadLockCount()); + lock.readLock().unlock(); + }}); + awaitTermination(t1); + + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() { + assertEquals(2, lock.getReadLockCount()); + lock.writeLock().lock(); + assertEquals(0, lock.getReadLockCount()); + lock.writeLock().unlock(); + }}); + waitForQueuedThread(lock, t2); + assertNotWriteLocked(lock); + assertEquals(2, lock.getReadLockCount()); + lock.readLock().unlock(); + lock.readLock().unlock(); + assertEquals(0, lock.getReadLockCount()); + awaitTermination(t2); + assertNotWriteLocked(lock); + } + + /** + * A thread that tries to acquire a fair read lock (non-reentrantly) + * will block if there is a waiting writer thread + */ + public void testReaderWriterReaderFairFifo() { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(true); + final AtomicBoolean t1GotLock = new AtomicBoolean(false); + + lock.readLock().lock(); + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() { + assertEquals(1, lock.getReadLockCount()); + lock.writeLock().lock(); + assertEquals(0, lock.getReadLockCount()); + t1GotLock.set(true); + lock.writeLock().unlock(); + }}); + waitForQueuedThread(lock, t1); + + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() { + assertEquals(1, lock.getReadLockCount()); + lock.readLock().lock(); + assertEquals(1, lock.getReadLockCount()); + assertTrue(t1GotLock.get()); + lock.readLock().unlock(); + }}); + waitForQueuedThread(lock, t2); + assertTrue(t1.isAlive()); + assertNotWriteLocked(lock); + assertEquals(1, lock.getReadLockCount()); + lock.readLock().unlock(); + awaitTermination(t1); + awaitTermination(t2); + assertNotWriteLocked(lock); + } + + /** + * Readlocks succeed only after a writing thread unlocks + */ + public void testReadAfterWriteLock() { testReadAfterWriteLock(false); } + public void testReadAfterWriteLock_fair() { testReadAfterWriteLock(true); } + public void testReadAfterWriteLock(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + lock.writeLock().lock(); + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() { + lock.readLock().lock(); + lock.readLock().unlock(); + }}); + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() { + lock.readLock().lock(); + lock.readLock().unlock(); + }}); + + waitForQueuedThread(lock, t1); + waitForQueuedThread(lock, t2); + releaseWriteLock(lock); + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * Read trylock succeeds if write locked by current thread + */ + public void testReadHoldingWriteLock() { testReadHoldingWriteLock(false); } + public void testReadHoldingWriteLock_fair() { testReadHoldingWriteLock(true); } + public void testReadHoldingWriteLock(boolean fair) { + final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair); + lock.writeLock().lock(); + assertTrue(lock.readLock().tryLock()); + lock.readLock().unlock(); + lock.writeLock().unlock(); + } + + /** + * Read trylock succeeds (barging) even in the presence of waiting + * readers and/or writers + */ + public void testReadTryLockBarging() { testReadTryLockBarging(false); } + public void testReadTryLockBarging_fair() { testReadTryLockBarging(true); } + public void testReadTryLockBarging(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + lock.readLock().lock(); + + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() { + lock.writeLock().lock(); + lock.writeLock().unlock(); + }}); + + waitForQueuedThread(lock, t1); + + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() { + lock.readLock().lock(); + lock.readLock().unlock(); + }}); + + if (fair) + waitForQueuedThread(lock, t2); + + Thread t3 = newStartedThread(new CheckedRunnable() { + public void realRun() { + lock.readLock().tryLock(); + lock.readLock().unlock(); + }}); + + assertTrue(lock.getReadLockCount() > 0); + awaitTermination(t3); + assertTrue(t1.isAlive()); + if (fair) assertTrue(t2.isAlive()); + lock.readLock().unlock(); + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * Read lock succeeds if write locked by current thread even if + * other threads are waiting for readlock + */ + public void testReadHoldingWriteLock2() { testReadHoldingWriteLock2(false); } + public void testReadHoldingWriteLock2_fair() { testReadHoldingWriteLock2(true); } + public void testReadHoldingWriteLock2(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + lock.writeLock().lock(); + lock.readLock().lock(); + lock.readLock().unlock(); + + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() { + lock.readLock().lock(); + lock.readLock().unlock(); + }}); + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() { + lock.readLock().lock(); + lock.readLock().unlock(); + }}); + + waitForQueuedThread(lock, t1); + waitForQueuedThread(lock, t2); + assertWriteLockedByMoi(lock); + lock.readLock().lock(); + lock.readLock().unlock(); + releaseWriteLock(lock); + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * Read lock succeeds if write locked by current thread even if + * other threads are waiting for writelock + */ + public void testReadHoldingWriteLock3() { testReadHoldingWriteLock3(false); } + public void testReadHoldingWriteLock3_fair() { testReadHoldingWriteLock3(true); } + public void testReadHoldingWriteLock3(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + lock.writeLock().lock(); + lock.readLock().lock(); + lock.readLock().unlock(); + + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() { + lock.writeLock().lock(); + lock.writeLock().unlock(); + }}); + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() { + lock.writeLock().lock(); + lock.writeLock().unlock(); + }}); + + waitForQueuedThread(lock, t1); + waitForQueuedThread(lock, t2); + assertWriteLockedByMoi(lock); + lock.readLock().lock(); + lock.readLock().unlock(); + assertWriteLockedByMoi(lock); + lock.writeLock().unlock(); + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * Write lock succeeds if write locked by current thread even if + * other threads are waiting for writelock + */ + public void testWriteHoldingWriteLock4() { testWriteHoldingWriteLock4(false); } + public void testWriteHoldingWriteLock4_fair() { testWriteHoldingWriteLock4(true); } + public void testWriteHoldingWriteLock4(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + lock.writeLock().lock(); + lock.writeLock().lock(); + lock.writeLock().unlock(); + + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() { + lock.writeLock().lock(); + lock.writeLock().unlock(); + }}); + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() { + lock.writeLock().lock(); + lock.writeLock().unlock(); + }}); + + waitForQueuedThread(lock, t1); + waitForQueuedThread(lock, t2); + assertWriteLockedByMoi(lock); + assertEquals(1, lock.getWriteHoldCount()); + lock.writeLock().lock(); + assertWriteLockedByMoi(lock); + assertEquals(2, lock.getWriteHoldCount()); + lock.writeLock().unlock(); + assertWriteLockedByMoi(lock); + assertEquals(1, lock.getWriteHoldCount()); + lock.writeLock().unlock(); + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * Read tryLock succeeds if readlocked but not writelocked + */ + public void testTryLockWhenReadLocked() { testTryLockWhenReadLocked(false); } + public void testTryLockWhenReadLocked_fair() { testTryLockWhenReadLocked(true); } + public void testTryLockWhenReadLocked(boolean fair) { + final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair); + lock.readLock().lock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + assertTrue(lock.readLock().tryLock()); + lock.readLock().unlock(); + }}); + + awaitTermination(t); + lock.readLock().unlock(); + } + + /** + * write tryLock fails when readlocked + */ + public void testWriteTryLockWhenReadLocked() { testWriteTryLockWhenReadLocked(false); } + public void testWriteTryLockWhenReadLocked_fair() { testWriteTryLockWhenReadLocked(true); } + public void testWriteTryLockWhenReadLocked(boolean fair) { + final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair); + lock.readLock().lock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + assertFalse(lock.writeLock().tryLock()); + }}); + + awaitTermination(t); + lock.readLock().unlock(); + } + + /** + * write timed tryLock times out if locked + */ + public void testWriteTryLock_Timeout() { testWriteTryLock_Timeout(false); } + public void testWriteTryLock_Timeout_fair() { testWriteTryLock_Timeout(true); } + public void testWriteTryLock_Timeout(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + lock.writeLock().lock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + long timeoutMillis = 10; + assertFalse(lock.writeLock().tryLock(timeoutMillis, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + }}); + + awaitTermination(t); + releaseWriteLock(lock); + } + + /** + * read timed tryLock times out if write-locked + */ + public void testReadTryLock_Timeout() { testReadTryLock_Timeout(false); } + public void testReadTryLock_Timeout_fair() { testReadTryLock_Timeout(true); } + public void testReadTryLock_Timeout(boolean fair) { + final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair); + lock.writeLock().lock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + long timeoutMillis = 10; + assertFalse(lock.readLock().tryLock(timeoutMillis, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + }}); + + awaitTermination(t); + assertTrue(lock.writeLock().isHeldByCurrentThread()); + lock.writeLock().unlock(); + } + + /** + * write lockInterruptibly succeeds if unlocked, else is interruptible + */ + public void testWriteLockInterruptibly() { testWriteLockInterruptibly(false); } + public void testWriteLockInterruptibly_fair() { testWriteLockInterruptibly(true); } + public void testWriteLockInterruptibly(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + try { + lock.writeLock().lockInterruptibly(); + } catch (InterruptedException fail) { threadUnexpectedException(fail); } + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + lock.writeLock().lockInterruptibly(); + }}); + + waitForQueuedThread(lock, t); + t.interrupt(); + assertTrue(lock.writeLock().isHeldByCurrentThread()); + awaitTermination(t); + releaseWriteLock(lock); + } + + /** + * read lockInterruptibly succeeds if lock free else is interruptible + */ + public void testReadLockInterruptibly() { testReadLockInterruptibly(false); } + public void testReadLockInterruptibly_fair() { testReadLockInterruptibly(true); } + public void testReadLockInterruptibly(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + try { + lock.readLock().lockInterruptibly(); + lock.readLock().unlock(); + lock.writeLock().lockInterruptibly(); + } catch (InterruptedException fail) { threadUnexpectedException(fail); } + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + lock.readLock().lockInterruptibly(); + }}); + + waitForQueuedThread(lock, t); + t.interrupt(); + awaitTermination(t); + releaseWriteLock(lock); + } + + /** + * Calling await without holding lock throws IllegalMonitorStateException + */ + public void testAwait_IMSE() { testAwait_IMSE(false); } + public void testAwait_IMSE_fair() { testAwait_IMSE(true); } + public void testAwait_IMSE(boolean fair) { + final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair); + final Condition c = lock.writeLock().newCondition(); + for (AwaitMethod awaitMethod : AwaitMethod.values()) { + long startTime = System.nanoTime(); + try { + await(c, awaitMethod); + shouldThrow(); + } catch (IllegalMonitorStateException success) { + } catch (InterruptedException fail) { + threadUnexpectedException(fail); + } + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + } + + /** + * Calling signal without holding lock throws IllegalMonitorStateException + */ + public void testSignal_IMSE() { testSignal_IMSE(false); } + public void testSignal_IMSE_fair() { testSignal_IMSE(true); } + public void testSignal_IMSE(boolean fair) { + final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair); + final Condition c = lock.writeLock().newCondition(); + try { + c.signal(); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * Calling signalAll without holding lock throws IllegalMonitorStateException + */ + public void testSignalAll_IMSE() { testSignalAll_IMSE(false); } + public void testSignalAll_IMSE_fair() { testSignalAll_IMSE(true); } + public void testSignalAll_IMSE(boolean fair) { + final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair); + final Condition c = lock.writeLock().newCondition(); + try { + c.signalAll(); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * awaitNanos without a signal times out + */ + public void testAwaitNanos_Timeout() { testAwaitNanos_Timeout(false); } + public void testAwaitNanos_Timeout_fair() { testAwaitNanos_Timeout(true); } + public void testAwaitNanos_Timeout(boolean fair) { + try { + final ReentrantReadWriteLock lock = + new ReentrantReadWriteLock(fair); + final Condition c = lock.writeLock().newCondition(); + lock.writeLock().lock(); + long startTime = System.nanoTime(); + long timeoutMillis = 10; + long timeoutNanos = MILLISECONDS.toNanos(timeoutMillis); + long nanosRemaining = c.awaitNanos(timeoutNanos); + assertTrue(nanosRemaining <= 0); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + lock.writeLock().unlock(); + } catch (InterruptedException fail) { threadUnexpectedException(fail); } + } + + /** + * timed await without a signal times out + */ + public void testAwait_Timeout() { testAwait_Timeout(false); } + public void testAwait_Timeout_fair() { testAwait_Timeout(true); } + public void testAwait_Timeout(boolean fair) { + try { + final ReentrantReadWriteLock lock = + new ReentrantReadWriteLock(fair); + final Condition c = lock.writeLock().newCondition(); + lock.writeLock().lock(); + long startTime = System.nanoTime(); + long timeoutMillis = 10; + assertFalse(c.await(timeoutMillis, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + lock.writeLock().unlock(); + } catch (InterruptedException fail) { threadUnexpectedException(fail); } + } + + /** + * awaitUntil without a signal times out + */ + public void testAwaitUntil_Timeout() { testAwaitUntil_Timeout(false); } + public void testAwaitUntil_Timeout_fair() { testAwaitUntil_Timeout(true); } + public void testAwaitUntil_Timeout(boolean fair) { + try { + final ReentrantReadWriteLock lock = + new ReentrantReadWriteLock(fair); + final Condition c = lock.writeLock().newCondition(); + lock.writeLock().lock(); + // We shouldn't assume that nanoTime and currentTimeMillis + // use the same time source, so don't use nanoTime here. + java.util.Date delayedDate = delayedDate(timeoutMillis()); + assertFalse(c.awaitUntil(delayedDate)); + assertTrue(new java.util.Date().getTime() >= delayedDate.getTime()); + lock.writeLock().unlock(); + } catch (InterruptedException fail) { threadUnexpectedException(fail); } + } + + /** + * await returns when signalled + */ + public void testAwait() { testAwait(false); } + public void testAwait_fair() { testAwait(true); } + public void testAwait(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + final Condition c = lock.writeLock().newCondition(); + final CountDownLatch locked = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + lock.writeLock().lock(); + locked.countDown(); + c.await(); + lock.writeLock().unlock(); + }}); + + await(locked); + lock.writeLock().lock(); + assertHasWaiters(lock, c, t); + c.signal(); + assertHasNoWaiters(lock, c); + assertTrue(t.isAlive()); + lock.writeLock().unlock(); + awaitTermination(t); + } + + /** + * awaitUninterruptibly is uninterruptible + */ + public void testAwaitUninterruptibly() { testAwaitUninterruptibly(false); } + public void testAwaitUninterruptibly_fair() { testAwaitUninterruptibly(true); } + public void testAwaitUninterruptibly(boolean fair) { + final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair); + final Condition c = lock.writeLock().newCondition(); + final CountDownLatch pleaseInterrupt = new CountDownLatch(2); + + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() { + // Interrupt before awaitUninterruptibly + lock.writeLock().lock(); + pleaseInterrupt.countDown(); + Thread.currentThread().interrupt(); + c.awaitUninterruptibly(); + assertTrue(Thread.interrupted()); + lock.writeLock().unlock(); + }}); + + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() { + // Interrupt during awaitUninterruptibly + lock.writeLock().lock(); + pleaseInterrupt.countDown(); + c.awaitUninterruptibly(); + assertTrue(Thread.interrupted()); + lock.writeLock().unlock(); + }}); + + await(pleaseInterrupt); + lock.writeLock().lock(); + lock.writeLock().unlock(); + t2.interrupt(); + + assertThreadStaysAlive(t1); + assertTrue(t2.isAlive()); + + lock.writeLock().lock(); + c.signalAll(); + lock.writeLock().unlock(); + + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * await/awaitNanos/awaitUntil is interruptible + */ + public void testInterruptible_await() { testInterruptible(false, AwaitMethod.await); } + public void testInterruptible_await_fair() { testInterruptible(true, AwaitMethod.await); } + public void testInterruptible_awaitTimed() { testInterruptible(false, AwaitMethod.awaitTimed); } + public void testInterruptible_awaitTimed_fair() { testInterruptible(true, AwaitMethod.awaitTimed); } + public void testInterruptible_awaitNanos() { testInterruptible(false, AwaitMethod.awaitNanos); } + public void testInterruptible_awaitNanos_fair() { testInterruptible(true, AwaitMethod.awaitNanos); } + public void testInterruptible_awaitUntil() { testInterruptible(false, AwaitMethod.awaitUntil); } + public void testInterruptible_awaitUntil_fair() { testInterruptible(true, AwaitMethod.awaitUntil); } + public void testInterruptible(boolean fair, final AwaitMethod awaitMethod) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + final Condition c = lock.writeLock().newCondition(); + final CountDownLatch locked = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + lock.writeLock().lock(); + assertWriteLockedByMoi(lock); + assertHasNoWaiters(lock, c); + locked.countDown(); + try { + await(c, awaitMethod); + } finally { + assertWriteLockedByMoi(lock); + assertHasNoWaiters(lock, c); + lock.writeLock().unlock(); + assertFalse(Thread.interrupted()); + } + }}); + + await(locked); + assertHasWaiters(lock, c, t); + t.interrupt(); + awaitTermination(t); + assertNotWriteLocked(lock); + } + + /** + * signalAll wakes up all threads + */ + public void testSignalAll_await() { testSignalAll(false, AwaitMethod.await); } + public void testSignalAll_await_fair() { testSignalAll(true, AwaitMethod.await); } + public void testSignalAll_awaitTimed() { testSignalAll(false, AwaitMethod.awaitTimed); } + public void testSignalAll_awaitTimed_fair() { testSignalAll(true, AwaitMethod.awaitTimed); } + public void testSignalAll_awaitNanos() { testSignalAll(false, AwaitMethod.awaitNanos); } + public void testSignalAll_awaitNanos_fair() { testSignalAll(true, AwaitMethod.awaitNanos); } + public void testSignalAll_awaitUntil() { testSignalAll(false, AwaitMethod.awaitUntil); } + public void testSignalAll_awaitUntil_fair() { testSignalAll(true, AwaitMethod.awaitUntil); } + public void testSignalAll(boolean fair, final AwaitMethod awaitMethod) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + final Condition c = lock.writeLock().newCondition(); + final CountDownLatch locked = new CountDownLatch(2); + final Lock writeLock = lock.writeLock(); + class Awaiter extends CheckedRunnable { + public void realRun() throws InterruptedException { + writeLock.lock(); + locked.countDown(); + await(c, awaitMethod); + writeLock.unlock(); + } + } + + Thread t1 = newStartedThread(new Awaiter()); + Thread t2 = newStartedThread(new Awaiter()); + + await(locked); + writeLock.lock(); + assertHasWaiters(lock, c, t1, t2); + c.signalAll(); + assertHasNoWaiters(lock, c); + writeLock.unlock(); + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * signal wakes up waiting threads in FIFO order + */ + public void testSignalWakesFifo() { testSignalWakesFifo(false); } + public void testSignalWakesFifo_fair() { testSignalWakesFifo(true); } + public void testSignalWakesFifo(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + final Condition c = lock.writeLock().newCondition(); + final CountDownLatch locked1 = new CountDownLatch(1); + final CountDownLatch locked2 = new CountDownLatch(1); + final Lock writeLock = lock.writeLock(); + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + writeLock.lock(); + locked1.countDown(); + c.await(); + writeLock.unlock(); + }}); + + await(locked1); + + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + writeLock.lock(); + locked2.countDown(); + c.await(); + writeLock.unlock(); + }}); + + await(locked2); + + writeLock.lock(); + assertHasWaiters(lock, c, t1, t2); + assertFalse(lock.hasQueuedThreads()); + c.signal(); + assertHasWaiters(lock, c, t2); + assertTrue(lock.hasQueuedThread(t1)); + assertFalse(lock.hasQueuedThread(t2)); + c.signal(); + assertHasNoWaiters(lock, c); + assertTrue(lock.hasQueuedThread(t1)); + assertTrue(lock.hasQueuedThread(t2)); + writeLock.unlock(); + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * await after multiple reentrant locking preserves lock count + */ + public void testAwaitLockCount() { testAwaitLockCount(false); } + public void testAwaitLockCount_fair() { testAwaitLockCount(true); } + public void testAwaitLockCount(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + final Condition c = lock.writeLock().newCondition(); + final CountDownLatch locked = new CountDownLatch(2); + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + lock.writeLock().lock(); + assertWriteLockedByMoi(lock); + assertEquals(1, lock.writeLock().getHoldCount()); + locked.countDown(); + c.await(); + assertWriteLockedByMoi(lock); + assertEquals(1, lock.writeLock().getHoldCount()); + lock.writeLock().unlock(); + }}); + + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + lock.writeLock().lock(); + lock.writeLock().lock(); + assertWriteLockedByMoi(lock); + assertEquals(2, lock.writeLock().getHoldCount()); + locked.countDown(); + c.await(); + assertWriteLockedByMoi(lock); + assertEquals(2, lock.writeLock().getHoldCount()); + lock.writeLock().unlock(); + lock.writeLock().unlock(); + }}); + + await(locked); + lock.writeLock().lock(); + assertHasWaiters(lock, c, t1, t2); + c.signalAll(); + assertHasNoWaiters(lock, c); + lock.writeLock().unlock(); + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * A serialized lock deserializes as unlocked + */ + public void testSerialization() { testSerialization(false); } + public void testSerialization_fair() { testSerialization(true); } + public void testSerialization(boolean fair) { + ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair); + lock.writeLock().lock(); + lock.readLock().lock(); + + ReentrantReadWriteLock clone = serialClone(lock); + assertEquals(lock.isFair(), clone.isFair()); + assertTrue(lock.isWriteLocked()); + assertFalse(clone.isWriteLocked()); + assertEquals(1, lock.getReadLockCount()); + assertEquals(0, clone.getReadLockCount()); + clone.writeLock().lock(); + clone.readLock().lock(); + assertTrue(clone.isWriteLocked()); + assertEquals(1, clone.getReadLockCount()); + clone.readLock().unlock(); + clone.writeLock().unlock(); + assertFalse(clone.isWriteLocked()); + assertEquals(1, lock.getReadLockCount()); + assertEquals(0, clone.getReadLockCount()); + } + + /** + * hasQueuedThreads reports whether there are waiting threads + */ + public void testHasQueuedThreads() { testHasQueuedThreads(false); } + public void testHasQueuedThreads_fair() { testHasQueuedThreads(true); } + public void testHasQueuedThreads(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + Thread t1 = new Thread(new InterruptedLockRunnable(lock)); + Thread t2 = new Thread(new InterruptibleLockRunnable(lock)); + assertFalse(lock.hasQueuedThreads()); + lock.writeLock().lock(); + assertFalse(lock.hasQueuedThreads()); + t1.start(); + waitForQueuedThread(lock, t1); + assertTrue(lock.hasQueuedThreads()); + t2.start(); + waitForQueuedThread(lock, t2); + assertTrue(lock.hasQueuedThreads()); + t1.interrupt(); + awaitTermination(t1); + assertTrue(lock.hasQueuedThreads()); + lock.writeLock().unlock(); + awaitTermination(t2); + assertFalse(lock.hasQueuedThreads()); + } + + /** + * hasQueuedThread(null) throws NPE + */ + public void testHasQueuedThreadNPE() { testHasQueuedThreadNPE(false); } + public void testHasQueuedThreadNPE_fair() { testHasQueuedThreadNPE(true); } + public void testHasQueuedThreadNPE(boolean fair) { + final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair); + try { + lock.hasQueuedThread(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * hasQueuedThread reports whether a thread is queued + */ + public void testHasQueuedThread() { testHasQueuedThread(false); } + public void testHasQueuedThread_fair() { testHasQueuedThread(true); } + public void testHasQueuedThread(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + Thread t1 = new Thread(new InterruptedLockRunnable(lock)); + Thread t2 = new Thread(new InterruptibleLockRunnable(lock)); + assertFalse(lock.hasQueuedThread(t1)); + assertFalse(lock.hasQueuedThread(t2)); + lock.writeLock().lock(); + t1.start(); + waitForQueuedThread(lock, t1); + assertTrue(lock.hasQueuedThread(t1)); + assertFalse(lock.hasQueuedThread(t2)); + t2.start(); + waitForQueuedThread(lock, t2); + assertTrue(lock.hasQueuedThread(t1)); + assertTrue(lock.hasQueuedThread(t2)); + t1.interrupt(); + awaitTermination(t1); + assertFalse(lock.hasQueuedThread(t1)); + assertTrue(lock.hasQueuedThread(t2)); + lock.writeLock().unlock(); + awaitTermination(t2); + assertFalse(lock.hasQueuedThread(t1)); + assertFalse(lock.hasQueuedThread(t2)); + } + + /** + * getQueueLength reports number of waiting threads + */ + public void testGetQueueLength() { testGetQueueLength(false); } + public void testGetQueueLength_fair() { testGetQueueLength(true); } + public void testGetQueueLength(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + Thread t1 = new Thread(new InterruptedLockRunnable(lock)); + Thread t2 = new Thread(new InterruptibleLockRunnable(lock)); + assertEquals(0, lock.getQueueLength()); + lock.writeLock().lock(); + t1.start(); + waitForQueuedThread(lock, t1); + assertEquals(1, lock.getQueueLength()); + t2.start(); + waitForQueuedThread(lock, t2); + assertEquals(2, lock.getQueueLength()); + t1.interrupt(); + awaitTermination(t1); + assertEquals(1, lock.getQueueLength()); + lock.writeLock().unlock(); + awaitTermination(t2); + assertEquals(0, lock.getQueueLength()); + } + + /** + * getQueuedThreads includes waiting threads + */ + public void testGetQueuedThreads() { testGetQueuedThreads(false); } + public void testGetQueuedThreads_fair() { testGetQueuedThreads(true); } + public void testGetQueuedThreads(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + Thread t1 = new Thread(new InterruptedLockRunnable(lock)); + Thread t2 = new Thread(new InterruptibleLockRunnable(lock)); + assertTrue(lock.getQueuedThreads().isEmpty()); + lock.writeLock().lock(); + assertTrue(lock.getQueuedThreads().isEmpty()); + t1.start(); + waitForQueuedThread(lock, t1); + assertEquals(1, lock.getQueuedThreads().size()); + assertTrue(lock.getQueuedThreads().contains(t1)); + t2.start(); + waitForQueuedThread(lock, t2); + assertEquals(2, lock.getQueuedThreads().size()); + assertTrue(lock.getQueuedThreads().contains(t1)); + assertTrue(lock.getQueuedThreads().contains(t2)); + t1.interrupt(); + awaitTermination(t1); + assertFalse(lock.getQueuedThreads().contains(t1)); + assertTrue(lock.getQueuedThreads().contains(t2)); + assertEquals(1, lock.getQueuedThreads().size()); + lock.writeLock().unlock(); + awaitTermination(t2); + assertTrue(lock.getQueuedThreads().isEmpty()); + } + + /** + * hasWaiters throws NPE if null + */ + public void testHasWaitersNPE() { testHasWaitersNPE(false); } + public void testHasWaitersNPE_fair() { testHasWaitersNPE(true); } + public void testHasWaitersNPE(boolean fair) { + final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair); + try { + lock.hasWaiters(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * getWaitQueueLength throws NPE if null + */ + public void testGetWaitQueueLengthNPE() { testGetWaitQueueLengthNPE(false); } + public void testGetWaitQueueLengthNPE_fair() { testGetWaitQueueLengthNPE(true); } + public void testGetWaitQueueLengthNPE(boolean fair) { + final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair); + try { + lock.getWaitQueueLength(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * getWaitingThreads throws NPE if null + */ + public void testGetWaitingThreadsNPE() { testGetWaitingThreadsNPE(false); } + public void testGetWaitingThreadsNPE_fair() { testGetWaitingThreadsNPE(true); } + public void testGetWaitingThreadsNPE(boolean fair) { + final PublicReentrantReadWriteLock lock = new PublicReentrantReadWriteLock(fair); + try { + lock.getWaitingThreads(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * hasWaiters throws IllegalArgumentException if not owned + */ + public void testHasWaitersIAE() { testHasWaitersIAE(false); } + public void testHasWaitersIAE_fair() { testHasWaitersIAE(true); } + public void testHasWaitersIAE(boolean fair) { + final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair); + final Condition c = lock.writeLock().newCondition(); + final ReentrantReadWriteLock lock2 = new ReentrantReadWriteLock(fair); + try { + lock2.hasWaiters(c); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * hasWaiters throws IllegalMonitorStateException if not locked + */ + public void testHasWaitersIMSE() { testHasWaitersIMSE(false); } + public void testHasWaitersIMSE_fair() { testHasWaitersIMSE(true); } + public void testHasWaitersIMSE(boolean fair) { + final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair); + final Condition c = lock.writeLock().newCondition(); + try { + lock.hasWaiters(c); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * getWaitQueueLength throws IllegalArgumentException if not owned + */ + public void testGetWaitQueueLengthIAE() { testGetWaitQueueLengthIAE(false); } + public void testGetWaitQueueLengthIAE_fair() { testGetWaitQueueLengthIAE(true); } + public void testGetWaitQueueLengthIAE(boolean fair) { + final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair); + final Condition c = lock.writeLock().newCondition(); + final ReentrantReadWriteLock lock2 = new ReentrantReadWriteLock(fair); + try { + lock2.getWaitQueueLength(c); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * getWaitQueueLength throws IllegalMonitorStateException if not locked + */ + public void testGetWaitQueueLengthIMSE() { testGetWaitQueueLengthIMSE(false); } + public void testGetWaitQueueLengthIMSE_fair() { testGetWaitQueueLengthIMSE(true); } + public void testGetWaitQueueLengthIMSE(boolean fair) { + final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair); + final Condition c = lock.writeLock().newCondition(); + try { + lock.getWaitQueueLength(c); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * getWaitingThreads throws IllegalArgumentException if not owned + */ + public void testGetWaitingThreadsIAE() { testGetWaitingThreadsIAE(false); } + public void testGetWaitingThreadsIAE_fair() { testGetWaitingThreadsIAE(true); } + public void testGetWaitingThreadsIAE(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + final Condition c = lock.writeLock().newCondition(); + final PublicReentrantReadWriteLock lock2 = + new PublicReentrantReadWriteLock(fair); + try { + lock2.getWaitingThreads(c); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * getWaitingThreads throws IllegalMonitorStateException if not locked + */ + public void testGetWaitingThreadsIMSE() { testGetWaitingThreadsIMSE(false); } + public void testGetWaitingThreadsIMSE_fair() { testGetWaitingThreadsIMSE(true); } + public void testGetWaitingThreadsIMSE(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + final Condition c = lock.writeLock().newCondition(); + try { + lock.getWaitingThreads(c); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * hasWaiters returns true when a thread is waiting, else false + */ + public void testHasWaiters() { testHasWaiters(false); } + public void testHasWaiters_fair() { testHasWaiters(true); } + public void testHasWaiters(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + final Condition c = lock.writeLock().newCondition(); + final CountDownLatch locked = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + lock.writeLock().lock(); + assertHasNoWaiters(lock, c); + assertFalse(lock.hasWaiters(c)); + locked.countDown(); + c.await(); + assertHasNoWaiters(lock, c); + assertFalse(lock.hasWaiters(c)); + lock.writeLock().unlock(); + }}); + + await(locked); + lock.writeLock().lock(); + assertHasWaiters(lock, c, t); + assertTrue(lock.hasWaiters(c)); + c.signal(); + assertHasNoWaiters(lock, c); + assertFalse(lock.hasWaiters(c)); + lock.writeLock().unlock(); + awaitTermination(t); + assertHasNoWaiters(lock, c); + } + + /** + * getWaitQueueLength returns number of waiting threads + */ + public void testGetWaitQueueLength() { testGetWaitQueueLength(false); } + public void testGetWaitQueueLength_fair() { testGetWaitQueueLength(true); } + public void testGetWaitQueueLength(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + final Condition c = lock.writeLock().newCondition(); + final CountDownLatch locked = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + lock.writeLock().lock(); + assertEquals(0, lock.getWaitQueueLength(c)); + locked.countDown(); + c.await(); + lock.writeLock().unlock(); + }}); + + await(locked); + lock.writeLock().lock(); + assertHasWaiters(lock, c, t); + assertEquals(1, lock.getWaitQueueLength(c)); + c.signal(); + assertHasNoWaiters(lock, c); + assertEquals(0, lock.getWaitQueueLength(c)); + lock.writeLock().unlock(); + awaitTermination(t); + } + + /** + * getWaitingThreads returns only and all waiting threads + */ + public void testGetWaitingThreads() { testGetWaitingThreads(false); } + public void testGetWaitingThreads_fair() { testGetWaitingThreads(true); } + public void testGetWaitingThreads(boolean fair) { + final PublicReentrantReadWriteLock lock = + new PublicReentrantReadWriteLock(fair); + final Condition c = lock.writeLock().newCondition(); + final CountDownLatch locked1 = new CountDownLatch(1); + final CountDownLatch locked2 = new CountDownLatch(1); + Thread t1 = new Thread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + lock.writeLock().lock(); + assertTrue(lock.getWaitingThreads(c).isEmpty()); + locked1.countDown(); + c.await(); + lock.writeLock().unlock(); + }}); + + Thread t2 = new Thread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + lock.writeLock().lock(); + assertFalse(lock.getWaitingThreads(c).isEmpty()); + locked2.countDown(); + c.await(); + lock.writeLock().unlock(); + }}); + + lock.writeLock().lock(); + assertTrue(lock.getWaitingThreads(c).isEmpty()); + lock.writeLock().unlock(); + + t1.start(); + await(locked1); + t2.start(); + await(locked2); + + lock.writeLock().lock(); + assertTrue(lock.hasWaiters(c)); + assertTrue(lock.getWaitingThreads(c).contains(t1)); + assertTrue(lock.getWaitingThreads(c).contains(t2)); + assertEquals(2, lock.getWaitingThreads(c).size()); + c.signalAll(); + assertHasNoWaiters(lock, c); + lock.writeLock().unlock(); + + awaitTermination(t1); + awaitTermination(t2); + + assertHasNoWaiters(lock, c); + } + + /** + * toString indicates current lock state + */ + public void testToString() { testToString(false); } + public void testToString_fair() { testToString(true); } + public void testToString(boolean fair) { + ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair); + assertTrue(lock.toString().contains("Write locks = 0")); + assertTrue(lock.toString().contains("Read locks = 0")); + lock.writeLock().lock(); + assertTrue(lock.toString().contains("Write locks = 1")); + assertTrue(lock.toString().contains("Read locks = 0")); + lock.writeLock().unlock(); + lock.readLock().lock(); + lock.readLock().lock(); + assertTrue(lock.toString().contains("Write locks = 0")); + assertTrue(lock.toString().contains("Read locks = 2")); + } + + /** + * readLock.toString indicates current lock state + */ + public void testReadLockToString() { testReadLockToString(false); } + public void testReadLockToString_fair() { testReadLockToString(true); } + public void testReadLockToString(boolean fair) { + ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair); + assertTrue(lock.readLock().toString().contains("Read locks = 0")); + lock.readLock().lock(); + lock.readLock().lock(); + assertTrue(lock.readLock().toString().contains("Read locks = 2")); + } + + /** + * writeLock.toString indicates current lock state + */ + public void testWriteLockToString() { testWriteLockToString(false); } + public void testWriteLockToString_fair() { testWriteLockToString(true); } + public void testWriteLockToString(boolean fair) { + ReentrantReadWriteLock lock = new ReentrantReadWriteLock(fair); + assertTrue(lock.writeLock().toString().contains("Unlocked")); + lock.writeLock().lock(); + assertTrue(lock.writeLock().toString().contains("Locked")); + lock.writeLock().unlock(); + assertTrue(lock.writeLock().toString().contains("Unlocked")); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/ScheduledExecutorSubclassTest.java b/jdk/test/java/util/concurrent/tck/ScheduledExecutorSubclassTest.java new file mode 100644 index 00000000000..50b7b171140 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ScheduledExecutorSubclassTest.java @@ -0,0 +1,1286 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Delayed; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.RunnableScheduledFuture; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ScheduledExecutorSubclassTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(ScheduledExecutorSubclassTest.class); + } + + static class CustomTask implements RunnableScheduledFuture { + RunnableScheduledFuture task; + volatile boolean ran; + CustomTask(RunnableScheduledFuture t) { task = t; } + public boolean isPeriodic() { return task.isPeriodic(); } + public void run() { + ran = true; + task.run(); + } + public long getDelay(TimeUnit unit) { return task.getDelay(unit); } + public int compareTo(Delayed t) { + return task.compareTo(((CustomTask)t).task); + } + public boolean cancel(boolean mayInterruptIfRunning) { + return task.cancel(mayInterruptIfRunning); + } + public boolean isCancelled() { return task.isCancelled(); } + public boolean isDone() { return task.isDone(); } + public V get() throws InterruptedException, ExecutionException { + V v = task.get(); + assertTrue(ran); + return v; + } + public V get(long time, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + V v = task.get(time, unit); + assertTrue(ran); + return v; + } + } + + public class CustomExecutor extends ScheduledThreadPoolExecutor { + + protected RunnableScheduledFuture decorateTask(Runnable r, RunnableScheduledFuture task) { + return new CustomTask(task); + } + + protected RunnableScheduledFuture decorateTask(Callable c, RunnableScheduledFuture task) { + return new CustomTask(task); + } + CustomExecutor(int corePoolSize) { super(corePoolSize); } + CustomExecutor(int corePoolSize, RejectedExecutionHandler handler) { + super(corePoolSize, handler); + } + + CustomExecutor(int corePoolSize, ThreadFactory threadFactory) { + super(corePoolSize, threadFactory); + } + CustomExecutor(int corePoolSize, ThreadFactory threadFactory, + RejectedExecutionHandler handler) { + super(corePoolSize, threadFactory, handler); + } + + } + + /** + * execute successfully executes a runnable + */ + public void testExecute() throws InterruptedException { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch done = new CountDownLatch(1); + final Runnable task = new CheckedRunnable() { + public void realRun() { done.countDown(); }}; + p.execute(task); + await(done); + } + } + + /** + * delayed schedule of callable successfully executes after delay + */ + public void testSchedule1() throws Exception { + final CountDownLatch done = new CountDownLatch(1); + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p, done)) { + final long startTime = System.nanoTime(); + Callable task = new CheckedCallable() { + public Boolean realCall() { + done.countDown(); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + return Boolean.TRUE; + }}; + Future f = p.schedule(task, timeoutMillis(), MILLISECONDS); + assertSame(Boolean.TRUE, f.get()); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + } + } + + /** + * delayed schedule of runnable successfully executes after delay + */ + public void testSchedule3() throws Exception { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + final long startTime = System.nanoTime(); + final CountDownLatch done = new CountDownLatch(1); + Runnable task = new CheckedRunnable() { + public void realRun() { + done.countDown(); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + }}; + Future f = p.schedule(task, timeoutMillis(), MILLISECONDS); + await(done); + assertNull(f.get(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + } + } + + /** + * scheduleAtFixedRate executes runnable after given initial delay + */ + public void testSchedule4() throws InterruptedException { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + final long startTime = System.nanoTime(); + final CountDownLatch done = new CountDownLatch(1); + Runnable task = new CheckedRunnable() { + public void realRun() { + done.countDown(); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + }}; + ScheduledFuture f = + p.scheduleAtFixedRate(task, timeoutMillis(), + LONG_DELAY_MS, MILLISECONDS); + await(done); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + f.cancel(true); + } + } + + /** + * scheduleWithFixedDelay executes runnable after given initial delay + */ + public void testSchedule5() throws InterruptedException { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + final long startTime = System.nanoTime(); + final CountDownLatch done = new CountDownLatch(1); + Runnable task = new CheckedRunnable() { + public void realRun() { + done.countDown(); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + }}; + ScheduledFuture f = + p.scheduleWithFixedDelay(task, timeoutMillis(), + LONG_DELAY_MS, MILLISECONDS); + await(done); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + f.cancel(true); + } + } + + static class RunnableCounter implements Runnable { + AtomicInteger count = new AtomicInteger(0); + public void run() { count.getAndIncrement(); } + } + + /** + * scheduleAtFixedRate executes series of tasks at given rate + */ + public void testFixedRateSequence() throws InterruptedException { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + for (int delay = 1; delay <= LONG_DELAY_MS; delay *= 3) { + long startTime = System.nanoTime(); + int cycles = 10; + final CountDownLatch done = new CountDownLatch(cycles); + Runnable task = new CheckedRunnable() { + public void realRun() { done.countDown(); }}; + ScheduledFuture h = + p.scheduleAtFixedRate(task, 0, delay, MILLISECONDS); + await(done); + h.cancel(true); + double normalizedTime = + (double) millisElapsedSince(startTime) / delay; + if (normalizedTime >= cycles - 1 && + normalizedTime <= cycles) + return; + } + fail("unexpected execution rate"); + } + } + + /** + * scheduleWithFixedDelay executes series of tasks with given period + */ + public void testFixedDelaySequence() throws InterruptedException { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + for (int delay = 1; delay <= LONG_DELAY_MS; delay *= 3) { + long startTime = System.nanoTime(); + int cycles = 10; + final CountDownLatch done = new CountDownLatch(cycles); + Runnable task = new CheckedRunnable() { + public void realRun() { done.countDown(); }}; + ScheduledFuture h = + p.scheduleWithFixedDelay(task, 0, delay, MILLISECONDS); + await(done); + h.cancel(true); + double normalizedTime = + (double) millisElapsedSince(startTime) / delay; + if (normalizedTime >= cycles - 1 && + normalizedTime <= cycles) + return; + } + fail("unexpected execution rate"); + } + } + + /** + * execute(null) throws NPE + */ + public void testExecuteNull() throws InterruptedException { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.execute(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * schedule(null) throws NPE + */ + public void testScheduleNull() throws InterruptedException { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + TrackedCallable callable = null; + Future f = p.schedule(callable, SHORT_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * execute throws RejectedExecutionException if shutdown + */ + public void testSchedule1_RejectedExecutionException() { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.shutdown(); + p.schedule(new NoOpRunnable(), + MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (RejectedExecutionException success) { + } catch (SecurityException ok) {} + } + } + + /** + * schedule throws RejectedExecutionException if shutdown + */ + public void testSchedule2_RejectedExecutionException() { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.shutdown(); + p.schedule(new NoOpCallable(), + MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (RejectedExecutionException success) { + } catch (SecurityException ok) {} + } + } + + /** + * schedule callable throws RejectedExecutionException if shutdown + */ + public void testSchedule3_RejectedExecutionException() { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.shutdown(); + p.schedule(new NoOpCallable(), + MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (RejectedExecutionException success) { + } catch (SecurityException ok) {} + } + } + + /** + * scheduleAtFixedRate throws RejectedExecutionException if shutdown + */ + public void testScheduleAtFixedRate1_RejectedExecutionException() { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.shutdown(); + p.scheduleAtFixedRate(new NoOpRunnable(), + MEDIUM_DELAY_MS, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (RejectedExecutionException success) { + } catch (SecurityException ok) {} + } + } + + /** + * scheduleWithFixedDelay throws RejectedExecutionException if shutdown + */ + public void testScheduleWithFixedDelay1_RejectedExecutionException() { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.shutdown(); + p.scheduleWithFixedDelay(new NoOpRunnable(), + MEDIUM_DELAY_MS, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (RejectedExecutionException success) { + } catch (SecurityException ok) {} + } + } + + /** + * getActiveCount increases but doesn't overestimate, when a + * thread becomes active + */ + public void testGetActiveCount() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + assertEquals(0, p.getActiveCount()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + assertEquals(1, p.getActiveCount()); + await(done); + }}); + await(threadStarted); + assertEquals(1, p.getActiveCount()); + } + } + + /** + * getCompletedTaskCount increases, but doesn't overestimate, + * when tasks complete + */ + public void testGetCompletedTaskCount() throws InterruptedException { + final ThreadPoolExecutor p = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + final CountDownLatch threadProceed = new CountDownLatch(1); + final CountDownLatch threadDone = new CountDownLatch(1); + assertEquals(0, p.getCompletedTaskCount()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + assertEquals(0, p.getCompletedTaskCount()); + threadProceed.await(); + threadDone.countDown(); + }}); + await(threadStarted); + assertEquals(0, p.getCompletedTaskCount()); + threadProceed.countDown(); + threadDone.await(); + long startTime = System.nanoTime(); + while (p.getCompletedTaskCount() != 1) { + if (millisElapsedSince(startTime) > LONG_DELAY_MS) + fail("timed out"); + Thread.yield(); + } + } + } + + /** + * getCorePoolSize returns size given in constructor if not otherwise set + */ + public void testGetCorePoolSize() { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(1, p.getCorePoolSize()); + } + } + + /** + * getLargestPoolSize increases, but doesn't overestimate, when + * multiple threads active + */ + public void testGetLargestPoolSize() throws InterruptedException { + final int THREADS = 3; + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = new CustomExecutor(THREADS); + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadsStarted = new CountDownLatch(THREADS); + assertEquals(0, p.getLargestPoolSize()); + for (int i = 0; i < THREADS; i++) + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.countDown(); + await(done); + assertEquals(THREADS, p.getLargestPoolSize()); + }}); + await(threadsStarted); + assertEquals(THREADS, p.getLargestPoolSize()); + } + assertEquals(THREADS, p.getLargestPoolSize()); + } + + /** + * getPoolSize increases, but doesn't overestimate, when threads + * become active + */ + public void testGetPoolSize() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + assertEquals(0, p.getPoolSize()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + assertEquals(1, p.getPoolSize()); + await(done); + }}); + await(threadStarted); + assertEquals(1, p.getPoolSize()); + } + } + + /** + * getTaskCount increases, but doesn't overestimate, when tasks + * submitted + */ + public void testGetTaskCount() throws InterruptedException { + final int TASKS = 3; + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + assertEquals(0, p.getTaskCount()); + assertEquals(0, p.getCompletedTaskCount()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + await(done); + }}); + await(threadStarted); + assertEquals(1, p.getTaskCount()); + assertEquals(0, p.getCompletedTaskCount()); + for (int i = 0; i < TASKS; i++) { + assertEquals(1 + i, p.getTaskCount()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + assertEquals(1 + TASKS, p.getTaskCount()); + await(done); + }}); + } + assertEquals(1 + TASKS, p.getTaskCount()); + assertEquals(0, p.getCompletedTaskCount()); + } + assertEquals(1 + TASKS, p.getTaskCount()); + assertEquals(1 + TASKS, p.getCompletedTaskCount()); + } + + /** + * getThreadFactory returns factory in constructor if not set + */ + public void testGetThreadFactory() { + final ThreadFactory threadFactory = new SimpleThreadFactory(); + final CustomExecutor p = new CustomExecutor(1, threadFactory); + try (PoolCleaner cleaner = cleaner(p)) { + assertSame(threadFactory, p.getThreadFactory()); + } + } + + /** + * setThreadFactory sets the thread factory returned by getThreadFactory + */ + public void testSetThreadFactory() { + final ThreadFactory threadFactory = new SimpleThreadFactory(); + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + p.setThreadFactory(threadFactory); + assertSame(threadFactory, p.getThreadFactory()); + } + } + + /** + * setThreadFactory(null) throws NPE + */ + public void testSetThreadFactoryNull() { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setThreadFactory(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * isShutdown is false before shutdown, true after + */ + public void testIsShutdown() { + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + assertFalse(p.isShutdown()); + try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.isShutdown()); + } + } + + /** + * isTerminated is false before termination, true after + */ + public void testIsTerminated() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(p.isTerminated()); + threadStarted.countDown(); + await(done); + }}); + await(threadStarted); + assertFalse(p.isTerminated()); + assertFalse(p.isTerminating()); + done.countDown(); + try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + } + } + + /** + * isTerminating is not true when running or when terminated + */ + public void testIsTerminating() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + assertFalse(p.isTerminating()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(p.isTerminating()); + threadStarted.countDown(); + await(done); + }}); + await(threadStarted); + assertFalse(p.isTerminating()); + done.countDown(); + try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + assertFalse(p.isTerminating()); + } + } + + /** + * getQueue returns the work queue, which contains queued tasks + */ + public void testGetQueue() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); + final ScheduledThreadPoolExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + ScheduledFuture[] tasks = new ScheduledFuture[5]; + for (int i = 0; i < tasks.length; i++) { + Runnable r = new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + await(done); + }}; + tasks[i] = p.schedule(r, 1, MILLISECONDS); + } + await(threadStarted); + BlockingQueue q = p.getQueue(); + assertTrue(q.contains(tasks[tasks.length - 1])); + assertFalse(q.contains(tasks[0])); + } + } + + /** + * remove(task) removes queued task, and fails to remove active task + */ + public void testRemove() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); + final ScheduledThreadPoolExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p, done)) { + ScheduledFuture[] tasks = new ScheduledFuture[5]; + final CountDownLatch threadStarted = new CountDownLatch(1); + for (int i = 0; i < tasks.length; i++) { + Runnable r = new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + await(done); + }}; + tasks[i] = p.schedule(r, 1, MILLISECONDS); + } + await(threadStarted); + BlockingQueue q = p.getQueue(); + assertFalse(p.remove((Runnable)tasks[0])); + assertTrue(q.contains((Runnable)tasks[4])); + assertTrue(q.contains((Runnable)tasks[3])); + assertTrue(p.remove((Runnable)tasks[4])); + assertFalse(p.remove((Runnable)tasks[4])); + assertFalse(q.contains((Runnable)tasks[4])); + assertTrue(q.contains((Runnable)tasks[3])); + assertTrue(p.remove((Runnable)tasks[3])); + assertFalse(q.contains((Runnable)tasks[3])); + } + } + + /** + * purge removes cancelled tasks from the queue + */ + public void testPurge() throws InterruptedException { + final ScheduledFuture[] tasks = new ScheduledFuture[5]; + final Runnable releaser = new Runnable() { public void run() { + for (ScheduledFuture task : tasks) + if (task != null) task.cancel(true); }}; + final CustomExecutor p = new CustomExecutor(1); + try (PoolCleaner cleaner = cleaner(p, releaser)) { + for (int i = 0; i < tasks.length; i++) + tasks[i] = p.schedule(new SmallPossiblyInterruptedRunnable(), + LONG_DELAY_MS, MILLISECONDS); + int max = tasks.length; + if (tasks[4].cancel(true)) --max; + if (tasks[3].cancel(true)) --max; + // There must eventually be an interference-free point at + // which purge will not fail. (At worst, when queue is empty.) + long startTime = System.nanoTime(); + do { + p.purge(); + long count = p.getTaskCount(); + if (count == max) + return; + } while (millisElapsedSince(startTime) < LONG_DELAY_MS); + fail("Purge failed to remove cancelled tasks"); + } + } + + /** + * shutdownNow returns a list containing tasks that were not run, + * and those tasks are drained from the queue + */ + public void testShutdownNow() throws InterruptedException { + final int poolSize = 2; + final int count = 5; + final AtomicInteger ran = new AtomicInteger(0); + final CustomExecutor p = new CustomExecutor(poolSize); + final CountDownLatch threadsStarted = new CountDownLatch(poolSize); + Runnable waiter = new CheckedRunnable() { public void realRun() { + threadsStarted.countDown(); + try { + MILLISECONDS.sleep(2 * LONG_DELAY_MS); + } catch (InterruptedException success) {} + ran.getAndIncrement(); + }}; + for (int i = 0; i < count; i++) + p.execute(waiter); + await(threadsStarted); + assertEquals(poolSize, p.getActiveCount()); + assertEquals(0, p.getCompletedTaskCount()); + final List queuedTasks; + try { + queuedTasks = p.shutdownNow(); + } catch (SecurityException ok) { + return; // Allowed in case test doesn't have privs + } + assertTrue(p.isShutdown()); + assertTrue(p.getQueue().isEmpty()); + assertEquals(count - poolSize, queuedTasks.size()); + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + assertEquals(poolSize, ran.get()); + assertEquals(poolSize, p.getCompletedTaskCount()); + } + + /** + * shutdownNow returns a list containing tasks that were not run, + * and those tasks are drained from the queue + */ + public void testShutdownNow_delayedTasks() throws InterruptedException { + final CustomExecutor p = new CustomExecutor(1); + List tasks = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + Runnable r = new NoOpRunnable(); + tasks.add(p.schedule(r, 9, SECONDS)); + tasks.add(p.scheduleAtFixedRate(r, 9, 9, SECONDS)); + tasks.add(p.scheduleWithFixedDelay(r, 9, 9, SECONDS)); + } + if (testImplementationDetails) + assertEquals(new HashSet(tasks), new HashSet(p.getQueue())); + final List queuedTasks; + try { + queuedTasks = p.shutdownNow(); + } catch (SecurityException ok) { + return; // Allowed in case test doesn't have privs + } + assertTrue(p.isShutdown()); + assertTrue(p.getQueue().isEmpty()); + if (testImplementationDetails) + assertEquals(new HashSet(tasks), new HashSet(queuedTasks)); + assertEquals(tasks.size(), queuedTasks.size()); + for (ScheduledFuture task : tasks) { + assertFalse(((CustomTask)task).ran); + assertFalse(task.isDone()); + assertFalse(task.isCancelled()); + } + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + } + + /** + * By default, periodic tasks are cancelled at shutdown. + * By default, delayed tasks keep running after shutdown. + * Check that changing the default values work: + * - setExecuteExistingDelayedTasksAfterShutdownPolicy + * - setContinueExistingPeriodicTasksAfterShutdownPolicy + */ + public void testShutdown_cancellation() throws Exception { + Boolean[] allBooleans = { null, Boolean.FALSE, Boolean.TRUE }; + for (Boolean policy : allBooleans) + { + final int poolSize = 2; + final CustomExecutor p = new CustomExecutor(poolSize); + final boolean effectiveDelayedPolicy = (policy != Boolean.FALSE); + final boolean effectivePeriodicPolicy = (policy == Boolean.TRUE); + final boolean effectiveRemovePolicy = (policy == Boolean.TRUE); + if (policy != null) { + p.setExecuteExistingDelayedTasksAfterShutdownPolicy(policy); + p.setContinueExistingPeriodicTasksAfterShutdownPolicy(policy); + p.setRemoveOnCancelPolicy(policy); + } + assertEquals(effectiveDelayedPolicy, + p.getExecuteExistingDelayedTasksAfterShutdownPolicy()); + assertEquals(effectivePeriodicPolicy, + p.getContinueExistingPeriodicTasksAfterShutdownPolicy()); + assertEquals(effectiveRemovePolicy, + p.getRemoveOnCancelPolicy()); + // Strategy: Wedge the pool with poolSize "blocker" threads + final AtomicInteger ran = new AtomicInteger(0); + final CountDownLatch poolBlocked = new CountDownLatch(poolSize); + final CountDownLatch unblock = new CountDownLatch(1); + final CountDownLatch periodicLatch1 = new CountDownLatch(2); + final CountDownLatch periodicLatch2 = new CountDownLatch(2); + Runnable task = new CheckedRunnable() { public void realRun() + throws InterruptedException { + poolBlocked.countDown(); + assertTrue(unblock.await(LONG_DELAY_MS, MILLISECONDS)); + ran.getAndIncrement(); + }}; + List> blockers = new ArrayList<>(); + List> periodics = new ArrayList<>(); + List> delayeds = new ArrayList<>(); + for (int i = 0; i < poolSize; i++) + blockers.add(p.submit(task)); + assertTrue(poolBlocked.await(LONG_DELAY_MS, MILLISECONDS)); + + periodics.add(p.scheduleAtFixedRate(countDowner(periodicLatch1), + 1, 1, MILLISECONDS)); + periodics.add(p.scheduleWithFixedDelay(countDowner(periodicLatch2), + 1, 1, MILLISECONDS)); + delayeds.add(p.schedule(task, 1, MILLISECONDS)); + + assertTrue(p.getQueue().containsAll(periodics)); + assertTrue(p.getQueue().containsAll(delayeds)); + try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.isShutdown()); + assertFalse(p.isTerminated()); + for (Future periodic : periodics) { + assertTrue(effectivePeriodicPolicy ^ periodic.isCancelled()); + assertTrue(effectivePeriodicPolicy ^ periodic.isDone()); + } + for (Future delayed : delayeds) { + assertTrue(effectiveDelayedPolicy ^ delayed.isCancelled()); + assertTrue(effectiveDelayedPolicy ^ delayed.isDone()); + } + if (testImplementationDetails) { + assertEquals(effectivePeriodicPolicy, + p.getQueue().containsAll(periodics)); + assertEquals(effectiveDelayedPolicy, + p.getQueue().containsAll(delayeds)); + } + // Release all pool threads + unblock.countDown(); + + for (Future delayed : delayeds) { + if (effectiveDelayedPolicy) { + assertNull(delayed.get()); + } + } + if (effectivePeriodicPolicy) { + assertTrue(periodicLatch1.await(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(periodicLatch2.await(LONG_DELAY_MS, MILLISECONDS)); + for (Future periodic : periodics) { + assertTrue(periodic.cancel(false)); + assertTrue(periodic.isCancelled()); + assertTrue(periodic.isDone()); + } + } + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + assertEquals(2 + (effectiveDelayedPolicy ? 1 : 0), ran.get()); + }} + + /** + * completed submit of callable returns result + */ + public void testSubmitCallable() throws Exception { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + Future future = e.submit(new StringTask()); + String result = future.get(); + assertSame(TEST_STRING, result); + } + } + + /** + * completed submit of runnable returns successfully + */ + public void testSubmitRunnable() throws Exception { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + Future future = e.submit(new NoOpRunnable()); + future.get(); + assertTrue(future.isDone()); + } + } + + /** + * completed submit of (runnable, result) returns result + */ + public void testSubmitRunnable2() throws Exception { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + Future future = e.submit(new NoOpRunnable(), TEST_STRING); + String result = future.get(); + assertSame(TEST_STRING, result); + } + } + + /** + * invokeAny(null) throws NPE + */ + public void testInvokeAny1() throws Exception { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * invokeAny(empty collection) throws IAE + */ + public void testInvokeAny2() throws Exception { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(new ArrayList>()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * invokeAny(c) throws NPE if c has null elements + */ + public void testInvokeAny3() throws Exception { + final CountDownLatch latch = new CountDownLatch(1); + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(latchAwaitingStringTask(latch)); + l.add(null); + try { + e.invokeAny(l); + shouldThrow(); + } catch (NullPointerException success) {} + latch.countDown(); + } + } + + /** + * invokeAny(c) throws ExecutionException if no task completes + */ + public void testInvokeAny4() throws Exception { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + try { + e.invokeAny(l); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + } + } + + /** + * invokeAny(c) returns result of some task + */ + public void testInvokeAny5() throws Exception { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(new StringTask()); + String result = e.invokeAny(l); + assertSame(TEST_STRING, result); + } + } + + /** + * invokeAll(null) throws NPE + */ + public void testInvokeAll1() throws Exception { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAll(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * invokeAll(empty collection) returns empty collection + */ + public void testInvokeAll2() throws Exception { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> r = e.invokeAll(new ArrayList>()); + assertTrue(r.isEmpty()); + } + } + + /** + * invokeAll(c) throws NPE if c has null elements + */ + public void testInvokeAll3() throws Exception { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(null); + try { + e.invokeAll(l); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * get of invokeAll(c) throws exception on failed task + */ + public void testInvokeAll4() throws Exception { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + List> futures = e.invokeAll(l); + assertEquals(1, futures.size()); + try { + futures.get(0).get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + } + } + + /** + * invokeAll(c) returns results of all completed tasks + */ + public void testInvokeAll5() throws Exception { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(new StringTask()); + List> futures = e.invokeAll(l); + assertEquals(2, futures.size()); + for (Future future : futures) + assertSame(TEST_STRING, future.get()); + } + } + + /** + * timed invokeAny(null) throws NPE + */ + public void testTimedInvokeAny1() throws Exception { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(null, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed invokeAny(,,null) throws NPE + */ + public void testTimedInvokeAnyNullTimeUnit() throws Exception { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + try { + e.invokeAny(l, MEDIUM_DELAY_MS, null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed invokeAny(empty collection) throws IAE + */ + public void testTimedInvokeAny2() throws Exception { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(new ArrayList>(), MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * timed invokeAny(c) throws NPE if c has null elements + */ + public void testTimedInvokeAny3() throws Exception { + CountDownLatch latch = new CountDownLatch(1); + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(latchAwaitingStringTask(latch)); + l.add(null); + try { + e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + latch.countDown(); + } + } + + /** + * timed invokeAny(c) throws ExecutionException if no task completes + */ + public void testTimedInvokeAny4() throws Exception { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + long startTime = System.nanoTime(); + List> l = new ArrayList>(); + l.add(new NPETask()); + try { + e.invokeAny(l, LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + } + + /** + * timed invokeAny(c) returns result of some task + */ + public void testTimedInvokeAny5() throws Exception { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + long startTime = System.nanoTime(); + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(new StringTask()); + String result = e.invokeAny(l, LONG_DELAY_MS, MILLISECONDS); + assertSame(TEST_STRING, result); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + } + + /** + * timed invokeAll(null) throws NPE + */ + public void testTimedInvokeAll1() throws Exception { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAll(null, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed invokeAll(,,null) throws NPE + */ + public void testTimedInvokeAllNullTimeUnit() throws Exception { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + try { + e.invokeAll(l, MEDIUM_DELAY_MS, null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed invokeAll(empty collection) returns empty collection + */ + public void testTimedInvokeAll2() throws Exception { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> r = e.invokeAll(new ArrayList>(), MEDIUM_DELAY_MS, MILLISECONDS); + assertTrue(r.isEmpty()); + } + } + + /** + * timed invokeAll(c) throws NPE if c has null elements + */ + public void testTimedInvokeAll3() throws Exception { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(null); + try { + e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * get of element of invokeAll(c) throws exception on failed task + */ + public void testTimedInvokeAll4() throws Exception { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + List> futures = + e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); + assertEquals(1, futures.size()); + try { + futures.get(0).get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + } + } + + /** + * timed invokeAll(c) returns results of all completed tasks + */ + public void testTimedInvokeAll5() throws Exception { + final ExecutorService e = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(new StringTask()); + List> futures = + e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS); + assertEquals(2, futures.size()); + for (Future future : futures) + assertSame(TEST_STRING, future.get()); + } + } + + /** + * timed invokeAll(c) cancels tasks not completed by timeout + */ + public void testTimedInvokeAll6() throws Exception { + for (long timeout = timeoutMillis();;) { + final CountDownLatch done = new CountDownLatch(1); + final Callable waiter = new CheckedCallable() { + public String realCall() { + try { done.await(LONG_DELAY_MS, MILLISECONDS); } + catch (InterruptedException ok) {} + return "1"; }}; + final ExecutorService p = new CustomExecutor(2); + try (PoolCleaner cleaner = cleaner(p, done)) { + List> tasks = new ArrayList<>(); + tasks.add(new StringTask("0")); + tasks.add(waiter); + tasks.add(new StringTask("2")); + long startTime = System.nanoTime(); + List> futures = + p.invokeAll(tasks, timeout, MILLISECONDS); + assertEquals(tasks.size(), futures.size()); + assertTrue(millisElapsedSince(startTime) >= timeout); + for (Future future : futures) + assertTrue(future.isDone()); + assertTrue(futures.get(1).isCancelled()); + try { + assertEquals("0", futures.get(0).get()); + assertEquals("2", futures.get(2).get()); + break; + } catch (CancellationException retryWithLongerTimeout) { + timeout *= 2; + if (timeout >= LONG_DELAY_MS / 2) + fail("expected exactly one task to be cancelled"); + } + } + } + } + +} diff --git a/jdk/test/java/util/concurrent/tck/ScheduledExecutorTest.java b/jdk/test/java/util/concurrent/tck/ScheduledExecutorTest.java new file mode 100644 index 00000000000..797dee3a845 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ScheduledExecutorTest.java @@ -0,0 +1,1259 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicInteger; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ScheduledExecutorTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(ScheduledExecutorTest.class); + } + + /** + * execute successfully executes a runnable + */ + public void testExecute() throws InterruptedException { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch done = new CountDownLatch(1); + final Runnable task = new CheckedRunnable() { + public void realRun() { done.countDown(); }}; + p.execute(task); + assertTrue(done.await(LONG_DELAY_MS, MILLISECONDS)); + } + } + + /** + * delayed schedule of callable successfully executes after delay + */ + public void testSchedule1() throws Exception { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + final long startTime = System.nanoTime(); + final CountDownLatch done = new CountDownLatch(1); + Callable task = new CheckedCallable() { + public Boolean realCall() { + done.countDown(); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + return Boolean.TRUE; + }}; + Future f = p.schedule(task, timeoutMillis(), MILLISECONDS); + assertSame(Boolean.TRUE, f.get()); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + assertTrue(done.await(0L, MILLISECONDS)); + } + } + + /** + * delayed schedule of runnable successfully executes after delay + */ + public void testSchedule3() throws Exception { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + final long startTime = System.nanoTime(); + final CountDownLatch done = new CountDownLatch(1); + Runnable task = new CheckedRunnable() { + public void realRun() { + done.countDown(); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + }}; + Future f = p.schedule(task, timeoutMillis(), MILLISECONDS); + await(done); + assertNull(f.get(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + } + } + + /** + * scheduleAtFixedRate executes runnable after given initial delay + */ + public void testSchedule4() throws Exception { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + final long startTime = System.nanoTime(); + final CountDownLatch done = new CountDownLatch(1); + Runnable task = new CheckedRunnable() { + public void realRun() { + done.countDown(); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + }}; + ScheduledFuture f = + p.scheduleAtFixedRate(task, timeoutMillis(), + LONG_DELAY_MS, MILLISECONDS); + await(done); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + f.cancel(true); + } + } + + /** + * scheduleWithFixedDelay executes runnable after given initial delay + */ + public void testSchedule5() throws Exception { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + final long startTime = System.nanoTime(); + final CountDownLatch done = new CountDownLatch(1); + Runnable task = new CheckedRunnable() { + public void realRun() { + done.countDown(); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + }}; + ScheduledFuture f = + p.scheduleWithFixedDelay(task, timeoutMillis(), + LONG_DELAY_MS, MILLISECONDS); + await(done); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + f.cancel(true); + } + } + + static class RunnableCounter implements Runnable { + AtomicInteger count = new AtomicInteger(0); + public void run() { count.getAndIncrement(); } + } + + /** + * scheduleAtFixedRate executes series of tasks at given rate + */ + public void testFixedRateSequence() throws InterruptedException { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + for (int delay = 1; delay <= LONG_DELAY_MS; delay *= 3) { + long startTime = System.nanoTime(); + int cycles = 10; + final CountDownLatch done = new CountDownLatch(cycles); + Runnable task = new CheckedRunnable() { + public void realRun() { done.countDown(); }}; + ScheduledFuture h = + p.scheduleAtFixedRate(task, 0, delay, MILLISECONDS); + await(done); + h.cancel(true); + double normalizedTime = + (double) millisElapsedSince(startTime) / delay; + if (normalizedTime >= cycles - 1 && + normalizedTime <= cycles) + return; + } + fail("unexpected execution rate"); + } + } + + /** + * scheduleWithFixedDelay executes series of tasks with given period + */ + public void testFixedDelaySequence() throws InterruptedException { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + for (int delay = 1; delay <= LONG_DELAY_MS; delay *= 3) { + long startTime = System.nanoTime(); + int cycles = 10; + final CountDownLatch done = new CountDownLatch(cycles); + Runnable task = new CheckedRunnable() { + public void realRun() { done.countDown(); }}; + ScheduledFuture h = + p.scheduleWithFixedDelay(task, 0, delay, MILLISECONDS); + await(done); + h.cancel(true); + double normalizedTime = + (double) millisElapsedSince(startTime) / delay; + if (normalizedTime >= cycles - 1 && + normalizedTime <= cycles) + return; + } + fail("unexpected execution rate"); + } + } + + /** + * execute(null) throws NPE + */ + public void testExecuteNull() throws InterruptedException { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.execute(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * schedule(null) throws NPE + */ + public void testScheduleNull() throws InterruptedException { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + TrackedCallable callable = null; + Future f = p.schedule(callable, SHORT_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * execute throws RejectedExecutionException if shutdown + */ + public void testSchedule1_RejectedExecutionException() throws InterruptedException { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.shutdown(); + p.schedule(new NoOpRunnable(), + MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (RejectedExecutionException success) { + } catch (SecurityException ok) {} + } + } + + /** + * schedule throws RejectedExecutionException if shutdown + */ + public void testSchedule2_RejectedExecutionException() throws InterruptedException { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.shutdown(); + p.schedule(new NoOpCallable(), + MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (RejectedExecutionException success) { + } catch (SecurityException ok) {} + } + } + + /** + * schedule callable throws RejectedExecutionException if shutdown + */ + public void testSchedule3_RejectedExecutionException() throws InterruptedException { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.shutdown(); + p.schedule(new NoOpCallable(), + MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (RejectedExecutionException success) { + } catch (SecurityException ok) {} + } + } + + /** + * scheduleAtFixedRate throws RejectedExecutionException if shutdown + */ + public void testScheduleAtFixedRate1_RejectedExecutionException() throws InterruptedException { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.shutdown(); + p.scheduleAtFixedRate(new NoOpRunnable(), + MEDIUM_DELAY_MS, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (RejectedExecutionException success) { + } catch (SecurityException ok) {} + } + } + + /** + * scheduleWithFixedDelay throws RejectedExecutionException if shutdown + */ + public void testScheduleWithFixedDelay1_RejectedExecutionException() throws InterruptedException { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.shutdown(); + p.scheduleWithFixedDelay(new NoOpRunnable(), + MEDIUM_DELAY_MS, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (RejectedExecutionException success) { + } catch (SecurityException ok) {} + } + } + + /** + * getActiveCount increases but doesn't overestimate, when a + * thread becomes active + */ + public void testGetActiveCount() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + assertEquals(0, p.getActiveCount()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + assertEquals(1, p.getActiveCount()); + await(done); + }}); + await(threadStarted); + assertEquals(1, p.getActiveCount()); + } + } + + /** + * getCompletedTaskCount increases, but doesn't overestimate, + * when tasks complete + */ + public void testGetCompletedTaskCount() throws InterruptedException { + final ThreadPoolExecutor p = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + final CountDownLatch threadProceed = new CountDownLatch(1); + final CountDownLatch threadDone = new CountDownLatch(1); + assertEquals(0, p.getCompletedTaskCount()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + assertEquals(0, p.getCompletedTaskCount()); + threadProceed.await(); + threadDone.countDown(); + }}); + await(threadStarted); + assertEquals(0, p.getCompletedTaskCount()); + threadProceed.countDown(); + threadDone.await(); + long startTime = System.nanoTime(); + while (p.getCompletedTaskCount() != 1) { + if (millisElapsedSince(startTime) > LONG_DELAY_MS) + fail("timed out"); + Thread.yield(); + } + } + } + + /** + * getCorePoolSize returns size given in constructor if not otherwise set + */ + public void testGetCorePoolSize() throws InterruptedException { + ThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(1, p.getCorePoolSize()); + } + } + + /** + * getLargestPoolSize increases, but doesn't overestimate, when + * multiple threads active + */ + public void testGetLargestPoolSize() throws InterruptedException { + final int THREADS = 3; + final ThreadPoolExecutor p = new ScheduledThreadPoolExecutor(THREADS); + final CountDownLatch threadsStarted = new CountDownLatch(THREADS); + final CountDownLatch done = new CountDownLatch(1); + try (PoolCleaner cleaner = cleaner(p, done)) { + assertEquals(0, p.getLargestPoolSize()); + for (int i = 0; i < THREADS; i++) + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.countDown(); + await(done); + assertEquals(THREADS, p.getLargestPoolSize()); + }}); + await(threadsStarted); + assertEquals(THREADS, p.getLargestPoolSize()); + } + assertEquals(THREADS, p.getLargestPoolSize()); + } + + /** + * getPoolSize increases, but doesn't overestimate, when threads + * become active + */ + public void testGetPoolSize() throws InterruptedException { + final ThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + final CountDownLatch threadStarted = new CountDownLatch(1); + final CountDownLatch done = new CountDownLatch(1); + try (PoolCleaner cleaner = cleaner(p, done)) { + assertEquals(0, p.getPoolSize()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + assertEquals(1, p.getPoolSize()); + await(done); + }}); + await(threadStarted); + assertEquals(1, p.getPoolSize()); + } + } + + /** + * getTaskCount increases, but doesn't overestimate, when tasks + * submitted + */ + public void testGetTaskCount() throws InterruptedException { + final int TASKS = 3; + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + assertEquals(0, p.getTaskCount()); + assertEquals(0, p.getCompletedTaskCount()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + await(done); + }}); + await(threadStarted); + assertEquals(1, p.getTaskCount()); + assertEquals(0, p.getCompletedTaskCount()); + for (int i = 0; i < TASKS; i++) { + assertEquals(1 + i, p.getTaskCount()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + assertEquals(1 + TASKS, p.getTaskCount()); + await(done); + }}); + } + assertEquals(1 + TASKS, p.getTaskCount()); + assertEquals(0, p.getCompletedTaskCount()); + } + assertEquals(1 + TASKS, p.getTaskCount()); + assertEquals(1 + TASKS, p.getCompletedTaskCount()); + } + + /** + * getThreadFactory returns factory in constructor if not set + */ + public void testGetThreadFactory() throws InterruptedException { + final ThreadFactory threadFactory = new SimpleThreadFactory(); + final ScheduledThreadPoolExecutor p = + new ScheduledThreadPoolExecutor(1, threadFactory); + try (PoolCleaner cleaner = cleaner(p)) { + assertSame(threadFactory, p.getThreadFactory()); + } + } + + /** + * setThreadFactory sets the thread factory returned by getThreadFactory + */ + public void testSetThreadFactory() throws InterruptedException { + ThreadFactory threadFactory = new SimpleThreadFactory(); + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + p.setThreadFactory(threadFactory); + assertSame(threadFactory, p.getThreadFactory()); + } + } + + /** + * setThreadFactory(null) throws NPE + */ + public void testSetThreadFactoryNull() throws InterruptedException { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setThreadFactory(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * isShutdown is false before shutdown, true after + */ + public void testIsShutdown() { + + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try { + assertFalse(p.isShutdown()); + } + finally { + try { p.shutdown(); } catch (SecurityException ok) { return; } + } + assertTrue(p.isShutdown()); + } + + /** + * isTerminated is false before termination, true after + */ + public void testIsTerminated() throws InterruptedException { + final ThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + final CountDownLatch done = new CountDownLatch(1); + assertFalse(p.isTerminated()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(p.isTerminated()); + threadStarted.countDown(); + await(done); + }}); + await(threadStarted); + assertFalse(p.isTerminating()); + done.countDown(); + try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + } + } + + /** + * isTerminating is not true when running or when terminated + */ + public void testIsTerminating() throws InterruptedException { + final ThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + final CountDownLatch threadStarted = new CountDownLatch(1); + final CountDownLatch done = new CountDownLatch(1); + try (PoolCleaner cleaner = cleaner(p)) { + assertFalse(p.isTerminating()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(p.isTerminating()); + threadStarted.countDown(); + await(done); + }}); + await(threadStarted); + assertFalse(p.isTerminating()); + done.countDown(); + try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + assertFalse(p.isTerminating()); + } + } + + /** + * getQueue returns the work queue, which contains queued tasks + */ + public void testGetQueue() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + ScheduledFuture[] tasks = new ScheduledFuture[5]; + for (int i = 0; i < tasks.length; i++) { + Runnable r = new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + await(done); + }}; + tasks[i] = p.schedule(r, 1, MILLISECONDS); + } + await(threadStarted); + BlockingQueue q = p.getQueue(); + assertTrue(q.contains(tasks[tasks.length - 1])); + assertFalse(q.contains(tasks[0])); + } + } + + /** + * remove(task) removes queued task, and fails to remove active task + */ + public void testRemove() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p, done)) { + ScheduledFuture[] tasks = new ScheduledFuture[5]; + final CountDownLatch threadStarted = new CountDownLatch(1); + for (int i = 0; i < tasks.length; i++) { + Runnable r = new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + await(done); + }}; + tasks[i] = p.schedule(r, 1, MILLISECONDS); + } + await(threadStarted); + BlockingQueue q = p.getQueue(); + assertFalse(p.remove((Runnable)tasks[0])); + assertTrue(q.contains((Runnable)tasks[4])); + assertTrue(q.contains((Runnable)tasks[3])); + assertTrue(p.remove((Runnable)tasks[4])); + assertFalse(p.remove((Runnable)tasks[4])); + assertFalse(q.contains((Runnable)tasks[4])); + assertTrue(q.contains((Runnable)tasks[3])); + assertTrue(p.remove((Runnable)tasks[3])); + assertFalse(q.contains((Runnable)tasks[3])); + } + } + + /** + * purge eventually removes cancelled tasks from the queue + */ + public void testPurge() throws InterruptedException { + final ScheduledFuture[] tasks = new ScheduledFuture[5]; + final Runnable releaser = new Runnable() { public void run() { + for (ScheduledFuture task : tasks) + if (task != null) task.cancel(true); }}; + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p, releaser)) { + for (int i = 0; i < tasks.length; i++) + tasks[i] = p.schedule(new SmallPossiblyInterruptedRunnable(), + LONG_DELAY_MS, MILLISECONDS); + int max = tasks.length; + if (tasks[4].cancel(true)) --max; + if (tasks[3].cancel(true)) --max; + // There must eventually be an interference-free point at + // which purge will not fail. (At worst, when queue is empty.) + long startTime = System.nanoTime(); + do { + p.purge(); + long count = p.getTaskCount(); + if (count == max) + return; + } while (millisElapsedSince(startTime) < LONG_DELAY_MS); + fail("Purge failed to remove cancelled tasks"); + } + } + + /** + * shutdownNow returns a list containing tasks that were not run, + * and those tasks are drained from the queue + */ + public void testShutdownNow() throws InterruptedException { + final int poolSize = 2; + final int count = 5; + final AtomicInteger ran = new AtomicInteger(0); + final ScheduledThreadPoolExecutor p = + new ScheduledThreadPoolExecutor(poolSize); + final CountDownLatch threadsStarted = new CountDownLatch(poolSize); + Runnable waiter = new CheckedRunnable() { public void realRun() { + threadsStarted.countDown(); + try { + MILLISECONDS.sleep(2 * LONG_DELAY_MS); + } catch (InterruptedException success) {} + ran.getAndIncrement(); + }}; + for (int i = 0; i < count; i++) + p.execute(waiter); + await(threadsStarted); + assertEquals(poolSize, p.getActiveCount()); + assertEquals(0, p.getCompletedTaskCount()); + final List queuedTasks; + try { + queuedTasks = p.shutdownNow(); + } catch (SecurityException ok) { + return; // Allowed in case test doesn't have privs + } + assertTrue(p.isShutdown()); + assertTrue(p.getQueue().isEmpty()); + assertEquals(count - poolSize, queuedTasks.size()); + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + assertEquals(poolSize, ran.get()); + assertEquals(poolSize, p.getCompletedTaskCount()); + } + + /** + * shutdownNow returns a list containing tasks that were not run, + * and those tasks are drained from the queue + */ + public void testShutdownNow_delayedTasks() throws InterruptedException { + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + List tasks = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + Runnable r = new NoOpRunnable(); + tasks.add(p.schedule(r, 9, SECONDS)); + tasks.add(p.scheduleAtFixedRate(r, 9, 9, SECONDS)); + tasks.add(p.scheduleWithFixedDelay(r, 9, 9, SECONDS)); + } + if (testImplementationDetails) + assertEquals(new HashSet(tasks), new HashSet(p.getQueue())); + final List queuedTasks; + try { + queuedTasks = p.shutdownNow(); + } catch (SecurityException ok) { + return; // Allowed in case test doesn't have privs + } + assertTrue(p.isShutdown()); + assertTrue(p.getQueue().isEmpty()); + if (testImplementationDetails) + assertEquals(new HashSet(tasks), new HashSet(queuedTasks)); + assertEquals(tasks.size(), queuedTasks.size()); + for (ScheduledFuture task : tasks) { + assertFalse(task.isDone()); + assertFalse(task.isCancelled()); + } + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + } + + /** + * By default, periodic tasks are cancelled at shutdown. + * By default, delayed tasks keep running after shutdown. + * Check that changing the default values work: + * - setExecuteExistingDelayedTasksAfterShutdownPolicy + * - setContinueExistingPeriodicTasksAfterShutdownPolicy + */ + public void testShutdown_cancellation() throws Exception { + Boolean[] allBooleans = { null, Boolean.FALSE, Boolean.TRUE }; + for (Boolean policy : allBooleans) + { + final int poolSize = 2; + final ScheduledThreadPoolExecutor p + = new ScheduledThreadPoolExecutor(poolSize); + final boolean effectiveDelayedPolicy = (policy != Boolean.FALSE); + final boolean effectivePeriodicPolicy = (policy == Boolean.TRUE); + final boolean effectiveRemovePolicy = (policy == Boolean.TRUE); + if (policy != null) { + p.setExecuteExistingDelayedTasksAfterShutdownPolicy(policy); + p.setContinueExistingPeriodicTasksAfterShutdownPolicy(policy); + p.setRemoveOnCancelPolicy(policy); + } + assertEquals(effectiveDelayedPolicy, + p.getExecuteExistingDelayedTasksAfterShutdownPolicy()); + assertEquals(effectivePeriodicPolicy, + p.getContinueExistingPeriodicTasksAfterShutdownPolicy()); + assertEquals(effectiveRemovePolicy, + p.getRemoveOnCancelPolicy()); + // Strategy: Wedge the pool with poolSize "blocker" threads + final AtomicInteger ran = new AtomicInteger(0); + final CountDownLatch poolBlocked = new CountDownLatch(poolSize); + final CountDownLatch unblock = new CountDownLatch(1); + final CountDownLatch periodicLatch1 = new CountDownLatch(2); + final CountDownLatch periodicLatch2 = new CountDownLatch(2); + Runnable task = new CheckedRunnable() { public void realRun() + throws InterruptedException { + poolBlocked.countDown(); + assertTrue(unblock.await(LONG_DELAY_MS, MILLISECONDS)); + ran.getAndIncrement(); + }}; + List> blockers = new ArrayList<>(); + List> periodics = new ArrayList<>(); + List> delayeds = new ArrayList<>(); + for (int i = 0; i < poolSize; i++) + blockers.add(p.submit(task)); + assertTrue(poolBlocked.await(LONG_DELAY_MS, MILLISECONDS)); + + periodics.add(p.scheduleAtFixedRate(countDowner(periodicLatch1), + 1, 1, MILLISECONDS)); + periodics.add(p.scheduleWithFixedDelay(countDowner(periodicLatch2), + 1, 1, MILLISECONDS)); + delayeds.add(p.schedule(task, 1, MILLISECONDS)); + + assertTrue(p.getQueue().containsAll(periodics)); + assertTrue(p.getQueue().containsAll(delayeds)); + try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.isShutdown()); + assertFalse(p.isTerminated()); + for (Future periodic : periodics) { + assertTrue(effectivePeriodicPolicy ^ periodic.isCancelled()); + assertTrue(effectivePeriodicPolicy ^ periodic.isDone()); + } + for (Future delayed : delayeds) { + assertTrue(effectiveDelayedPolicy ^ delayed.isCancelled()); + assertTrue(effectiveDelayedPolicy ^ delayed.isDone()); + } + if (testImplementationDetails) { + assertEquals(effectivePeriodicPolicy, + p.getQueue().containsAll(periodics)); + assertEquals(effectiveDelayedPolicy, + p.getQueue().containsAll(delayeds)); + } + // Release all pool threads + unblock.countDown(); + + for (Future delayed : delayeds) { + if (effectiveDelayedPolicy) { + assertNull(delayed.get()); + } + } + if (effectivePeriodicPolicy) { + assertTrue(periodicLatch1.await(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(periodicLatch2.await(LONG_DELAY_MS, MILLISECONDS)); + for (Future periodic : periodics) { + assertTrue(periodic.cancel(false)); + assertTrue(periodic.isCancelled()); + assertTrue(periodic.isDone()); + } + } + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + assertEquals(2 + (effectiveDelayedPolicy ? 1 : 0), ran.get()); + }} + + /** + * completed submit of callable returns result + */ + public void testSubmitCallable() throws Exception { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + Future future = e.submit(new StringTask()); + String result = future.get(); + assertSame(TEST_STRING, result); + } + } + + /** + * completed submit of runnable returns successfully + */ + public void testSubmitRunnable() throws Exception { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + Future future = e.submit(new NoOpRunnable()); + future.get(); + assertTrue(future.isDone()); + } + } + + /** + * completed submit of (runnable, result) returns result + */ + public void testSubmitRunnable2() throws Exception { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + Future future = e.submit(new NoOpRunnable(), TEST_STRING); + String result = future.get(); + assertSame(TEST_STRING, result); + } + } + + /** + * invokeAny(null) throws NPE + */ + public void testInvokeAny1() throws Exception { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * invokeAny(empty collection) throws IAE + */ + public void testInvokeAny2() throws Exception { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(new ArrayList>()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * invokeAny(c) throws NPE if c has null elements + */ + public void testInvokeAny3() throws Exception { + CountDownLatch latch = new CountDownLatch(1); + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(latchAwaitingStringTask(latch)); + l.add(null); + try { + e.invokeAny(l); + shouldThrow(); + } catch (NullPointerException success) {} + latch.countDown(); + } + } + + /** + * invokeAny(c) throws ExecutionException if no task completes + */ + public void testInvokeAny4() throws Exception { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + try { + e.invokeAny(l); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + } + } + + /** + * invokeAny(c) returns result of some task + */ + public void testInvokeAny5() throws Exception { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(new StringTask()); + String result = e.invokeAny(l); + assertSame(TEST_STRING, result); + } + } + + /** + * invokeAll(null) throws NPE + */ + public void testInvokeAll1() throws Exception { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAll(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * invokeAll(empty collection) returns empty collection + */ + public void testInvokeAll2() throws Exception { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> r = e.invokeAll(new ArrayList>()); + assertTrue(r.isEmpty()); + } + } + + /** + * invokeAll(c) throws NPE if c has null elements + */ + public void testInvokeAll3() throws Exception { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(null); + try { + e.invokeAll(l); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * get of invokeAll(c) throws exception on failed task + */ + public void testInvokeAll4() throws Exception { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + List> futures = e.invokeAll(l); + assertEquals(1, futures.size()); + try { + futures.get(0).get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + } + } + + /** + * invokeAll(c) returns results of all completed tasks + */ + public void testInvokeAll5() throws Exception { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(new StringTask()); + List> futures = e.invokeAll(l); + assertEquals(2, futures.size()); + for (Future future : futures) + assertSame(TEST_STRING, future.get()); + } + } + + /** + * timed invokeAny(null) throws NPE + */ + public void testTimedInvokeAny1() throws Exception { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(null, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed invokeAny(,,null) throws NPE + */ + public void testTimedInvokeAnyNullTimeUnit() throws Exception { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + try { + e.invokeAny(l, MEDIUM_DELAY_MS, null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed invokeAny(empty collection) throws IAE + */ + public void testTimedInvokeAny2() throws Exception { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(new ArrayList>(), MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * timed invokeAny(c) throws NPE if c has null elements + */ + public void testTimedInvokeAny3() throws Exception { + CountDownLatch latch = new CountDownLatch(1); + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(latchAwaitingStringTask(latch)); + l.add(null); + try { + e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + latch.countDown(); + } + } + + /** + * timed invokeAny(c) throws ExecutionException if no task completes + */ + public void testTimedInvokeAny4() throws Exception { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + long startTime = System.nanoTime(); + List> l = new ArrayList>(); + l.add(new NPETask()); + try { + e.invokeAny(l, LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + } + + /** + * timed invokeAny(c) returns result of some task + */ + public void testTimedInvokeAny5() throws Exception { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + long startTime = System.nanoTime(); + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(new StringTask()); + String result = e.invokeAny(l, LONG_DELAY_MS, MILLISECONDS); + assertSame(TEST_STRING, result); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + } + + /** + * timed invokeAll(null) throws NPE + */ + public void testTimedInvokeAll1() throws Exception { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAll(null, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed invokeAll(,,null) throws NPE + */ + public void testTimedInvokeAllNullTimeUnit() throws Exception { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + try { + e.invokeAll(l, MEDIUM_DELAY_MS, null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed invokeAll(empty collection) returns empty collection + */ + public void testTimedInvokeAll2() throws Exception { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> r = e.invokeAll(new ArrayList>(), + MEDIUM_DELAY_MS, MILLISECONDS); + assertTrue(r.isEmpty()); + } + } + + /** + * timed invokeAll(c) throws NPE if c has null elements + */ + public void testTimedInvokeAll3() throws Exception { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(null); + try { + e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * get of element of invokeAll(c) throws exception on failed task + */ + public void testTimedInvokeAll4() throws Exception { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + List> futures = + e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS); + assertEquals(1, futures.size()); + try { + futures.get(0).get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + } + } + + /** + * timed invokeAll(c) returns results of all completed tasks + */ + public void testTimedInvokeAll5() throws Exception { + final ExecutorService e = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(new StringTask()); + List> futures = + e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS); + assertEquals(2, futures.size()); + for (Future future : futures) + assertSame(TEST_STRING, future.get()); + } + } + + /** + * timed invokeAll(c) cancels tasks not completed by timeout + */ + public void testTimedInvokeAll6() throws Exception { + for (long timeout = timeoutMillis();;) { + final CountDownLatch done = new CountDownLatch(1); + final Callable waiter = new CheckedCallable() { + public String realCall() { + try { done.await(LONG_DELAY_MS, MILLISECONDS); } + catch (InterruptedException ok) {} + return "1"; }}; + final ExecutorService p = new ScheduledThreadPoolExecutor(2); + try (PoolCleaner cleaner = cleaner(p, done)) { + List> tasks = new ArrayList<>(); + tasks.add(new StringTask("0")); + tasks.add(waiter); + tasks.add(new StringTask("2")); + long startTime = System.nanoTime(); + List> futures = + p.invokeAll(tasks, timeout, MILLISECONDS); + assertEquals(tasks.size(), futures.size()); + assertTrue(millisElapsedSince(startTime) >= timeout); + for (Future future : futures) + assertTrue(future.isDone()); + assertTrue(futures.get(1).isCancelled()); + try { + assertEquals("0", futures.get(0).get()); + assertEquals("2", futures.get(2).get()); + break; + } catch (CancellationException retryWithLongerTimeout) { + timeout *= 2; + if (timeout >= LONG_DELAY_MS / 2) + fail("expected exactly one task to be cancelled"); + } + } + } + } + + /** + * A fixed delay task with overflowing period should not prevent a + * one-shot task from executing. + * https://bugs.openjdk.java.net/browse/JDK-8051859 + */ + public void testScheduleWithFixedDelay_overflow() throws Exception { + final CountDownLatch delayedDone = new CountDownLatch(1); + final CountDownLatch immediateDone = new CountDownLatch(1); + final ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1); + try (PoolCleaner cleaner = cleaner(p)) { + final Runnable immediate = new Runnable() { public void run() { + immediateDone.countDown(); + }}; + final Runnable delayed = new Runnable() { public void run() { + delayedDone.countDown(); + p.submit(immediate); + }}; + p.scheduleWithFixedDelay(delayed, 0L, Long.MAX_VALUE, SECONDS); + await(delayedDone); + await(immediateDone); + } + } + +} diff --git a/jdk/test/java/util/concurrent/tck/SemaphoreTest.java b/jdk/test/java/util/concurrent/tck/SemaphoreTest.java new file mode 100644 index 00000000000..0e85d7c73fc --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/SemaphoreTest.java @@ -0,0 +1,669 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.Collection; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Semaphore; + +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestSuite; + +public class SemaphoreTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(SemaphoreTest.class); + } + + /** + * Subclass to expose protected methods + */ + static class PublicSemaphore extends Semaphore { + PublicSemaphore(int permits) { super(permits); } + PublicSemaphore(int permits, boolean fair) { super(permits, fair); } + public Collection getQueuedThreads() { + return super.getQueuedThreads(); + } + public boolean hasQueuedThread(Thread t) { + return super.getQueuedThreads().contains(t); + } + public void reducePermits(int reduction) { + super.reducePermits(reduction); + } + } + + /** + * A runnable calling acquire + */ + class InterruptibleLockRunnable extends CheckedRunnable { + final Semaphore lock; + InterruptibleLockRunnable(Semaphore s) { lock = s; } + public void realRun() { + try { + lock.acquire(); + } + catch (InterruptedException ignored) {} + } + } + + /** + * A runnable calling acquire that expects to be interrupted + */ + class InterruptedLockRunnable extends CheckedInterruptedRunnable { + final Semaphore lock; + InterruptedLockRunnable(Semaphore s) { lock = s; } + public void realRun() throws InterruptedException { + lock.acquire(); + } + } + + /** + * Spin-waits until s.hasQueuedThread(t) becomes true. + */ + void waitForQueuedThread(PublicSemaphore s, Thread t) { + long startTime = System.nanoTime(); + while (!s.hasQueuedThread(t)) { + if (millisElapsedSince(startTime) > LONG_DELAY_MS) + throw new AssertionFailedError("timed out"); + Thread.yield(); + } + assertTrue(s.hasQueuedThreads()); + assertTrue(t.isAlive()); + } + + /** + * Spin-waits until s.hasQueuedThreads() becomes true. + */ + void waitForQueuedThreads(Semaphore s) { + long startTime = System.nanoTime(); + while (!s.hasQueuedThreads()) { + if (millisElapsedSince(startTime) > LONG_DELAY_MS) + throw new AssertionFailedError("timed out"); + Thread.yield(); + } + } + + enum AcquireMethod { + acquire() { + void acquire(Semaphore s) throws InterruptedException { + s.acquire(); + } + }, + acquireN() { + void acquire(Semaphore s, int permits) throws InterruptedException { + s.acquire(permits); + } + }, + acquireUninterruptibly() { + void acquire(Semaphore s) { + s.acquireUninterruptibly(); + } + }, + acquireUninterruptiblyN() { + void acquire(Semaphore s, int permits) { + s.acquireUninterruptibly(permits); + } + }, + tryAcquire() { + void acquire(Semaphore s) { + assertTrue(s.tryAcquire()); + } + }, + tryAcquireN() { + void acquire(Semaphore s, int permits) { + assertTrue(s.tryAcquire(permits)); + } + }, + tryAcquireTimed() { + void acquire(Semaphore s) throws InterruptedException { + assertTrue(s.tryAcquire(2 * LONG_DELAY_MS, MILLISECONDS)); + } + }, + tryAcquireTimedN { + void acquire(Semaphore s, int permits) throws InterruptedException { + assertTrue(s.tryAcquire(permits, 2 * LONG_DELAY_MS, MILLISECONDS)); + } + }; + + // Intentionally meta-circular + + /** Acquires 1 permit. */ + void acquire(Semaphore s) throws InterruptedException { + acquire(s, 1); + } + /** Acquires the given number of permits. */ + void acquire(Semaphore s, int permits) throws InterruptedException { + for (int i = 0; i < permits; i++) + acquire(s); + } + } + + /** + * Zero, negative, and positive initial values are allowed in constructor + */ + public void testConstructor() { testConstructor(false); } + public void testConstructor_fair() { testConstructor(true); } + public void testConstructor(boolean fair) { + for (int permits : new int[] { -42, -1, 0, 1, 42 }) { + Semaphore s = new Semaphore(permits, fair); + assertEquals(permits, s.availablePermits()); + assertEquals(fair, s.isFair()); + } + } + + /** + * Constructor without fairness argument behaves as nonfair + */ + public void testConstructorDefaultsToNonFair() { + for (int permits : new int[] { -42, -1, 0, 1, 42 }) { + Semaphore s = new Semaphore(permits); + assertEquals(permits, s.availablePermits()); + assertFalse(s.isFair()); + } + } + + /** + * tryAcquire succeeds when sufficient permits, else fails + */ + public void testTryAcquireInSameThread() { testTryAcquireInSameThread(false); } + public void testTryAcquireInSameThread_fair() { testTryAcquireInSameThread(true); } + public void testTryAcquireInSameThread(boolean fair) { + Semaphore s = new Semaphore(2, fair); + assertEquals(2, s.availablePermits()); + assertTrue(s.tryAcquire()); + assertTrue(s.tryAcquire()); + assertEquals(0, s.availablePermits()); + assertFalse(s.tryAcquire()); + assertFalse(s.tryAcquire()); + assertEquals(0, s.availablePermits()); + } + + /** + * timed tryAcquire times out + */ + public void testTryAcquire_timeout() { testTryAcquire_timeout(false); } + public void testTryAcquire_timeout_fair() { testTryAcquire_timeout(true); } + public void testTryAcquire_timeout(boolean fair) { + Semaphore s = new Semaphore(0, fair); + long startTime = System.nanoTime(); + try { assertFalse(s.tryAcquire(timeoutMillis(), MILLISECONDS)); } + catch (InterruptedException e) { threadUnexpectedException(e); } + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + } + + /** + * timed tryAcquire(N) times out + */ + public void testTryAcquireN_timeout() { testTryAcquireN_timeout(false); } + public void testTryAcquireN_timeout_fair() { testTryAcquireN_timeout(true); } + public void testTryAcquireN_timeout(boolean fair) { + Semaphore s = new Semaphore(2, fair); + long startTime = System.nanoTime(); + try { assertFalse(s.tryAcquire(3, timeoutMillis(), MILLISECONDS)); } + catch (InterruptedException e) { threadUnexpectedException(e); } + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + } + + /** + * acquire(), acquire(N), timed tryAcquired, timed tryAcquire(N) + * are interruptible + */ + public void testInterruptible_acquire() { testInterruptible(false, AcquireMethod.acquire); } + public void testInterruptible_acquire_fair() { testInterruptible(true, AcquireMethod.acquire); } + public void testInterruptible_acquireN() { testInterruptible(false, AcquireMethod.acquireN); } + public void testInterruptible_acquireN_fair() { testInterruptible(true, AcquireMethod.acquireN); } + public void testInterruptible_tryAcquireTimed() { testInterruptible(false, AcquireMethod.tryAcquireTimed); } + public void testInterruptible_tryAcquireTimed_fair() { testInterruptible(true, AcquireMethod.tryAcquireTimed); } + public void testInterruptible_tryAcquireTimedN() { testInterruptible(false, AcquireMethod.tryAcquireTimedN); } + public void testInterruptible_tryAcquireTimedN_fair() { testInterruptible(true, AcquireMethod.tryAcquireTimedN); } + public void testInterruptible(boolean fair, final AcquireMethod acquirer) { + final PublicSemaphore s = new PublicSemaphore(0, fair); + final Semaphore pleaseInterrupt = new Semaphore(0, fair); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + // Interrupt before acquire + Thread.currentThread().interrupt(); + try { + acquirer.acquire(s); + shouldThrow(); + } catch (InterruptedException success) {} + + // Interrupt during acquire + try { + acquirer.acquire(s); + shouldThrow(); + } catch (InterruptedException success) {} + + // Interrupt before acquire(N) + Thread.currentThread().interrupt(); + try { + acquirer.acquire(s, 3); + shouldThrow(); + } catch (InterruptedException success) {} + + pleaseInterrupt.release(); + + // Interrupt during acquire(N) + try { + acquirer.acquire(s, 3); + shouldThrow(); + } catch (InterruptedException success) {} + }}); + + waitForQueuedThread(s, t); + t.interrupt(); + await(pleaseInterrupt); + waitForQueuedThread(s, t); + t.interrupt(); + awaitTermination(t); + } + + /** + * acquireUninterruptibly(), acquireUninterruptibly(N) are + * uninterruptible + */ + public void testUninterruptible_acquireUninterruptibly() { testUninterruptible(false, AcquireMethod.acquireUninterruptibly); } + public void testUninterruptible_acquireUninterruptibly_fair() { testUninterruptible(true, AcquireMethod.acquireUninterruptibly); } + public void testUninterruptible_acquireUninterruptiblyN() { testUninterruptible(false, AcquireMethod.acquireUninterruptiblyN); } + public void testUninterruptible_acquireUninterruptiblyN_fair() { testUninterruptible(true, AcquireMethod.acquireUninterruptiblyN); } + public void testUninterruptible(boolean fair, final AcquireMethod acquirer) { + final PublicSemaphore s = new PublicSemaphore(0, fair); + final Semaphore pleaseInterrupt = new Semaphore(-1, fair); + + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + // Interrupt before acquire + pleaseInterrupt.release(); + Thread.currentThread().interrupt(); + acquirer.acquire(s); + assertTrue(Thread.interrupted()); + }}); + + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + // Interrupt during acquire + pleaseInterrupt.release(); + acquirer.acquire(s); + assertTrue(Thread.interrupted()); + }}); + + await(pleaseInterrupt); + waitForQueuedThread(s, t1); + waitForQueuedThread(s, t2); + t2.interrupt(); + + assertThreadStaysAlive(t1); + assertTrue(t2.isAlive()); + + s.release(2); + + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * hasQueuedThreads reports whether there are waiting threads + */ + public void testHasQueuedThreads() { testHasQueuedThreads(false); } + public void testHasQueuedThreads_fair() { testHasQueuedThreads(true); } + public void testHasQueuedThreads(boolean fair) { + final PublicSemaphore lock = new PublicSemaphore(1, fair); + assertFalse(lock.hasQueuedThreads()); + lock.acquireUninterruptibly(); + Thread t1 = newStartedThread(new InterruptedLockRunnable(lock)); + waitForQueuedThread(lock, t1); + assertTrue(lock.hasQueuedThreads()); + Thread t2 = newStartedThread(new InterruptibleLockRunnable(lock)); + waitForQueuedThread(lock, t2); + assertTrue(lock.hasQueuedThreads()); + t1.interrupt(); + awaitTermination(t1); + assertTrue(lock.hasQueuedThreads()); + lock.release(); + awaitTermination(t2); + assertFalse(lock.hasQueuedThreads()); + } + + /** + * getQueueLength reports number of waiting threads + */ + public void testGetQueueLength() { testGetQueueLength(false); } + public void testGetQueueLength_fair() { testGetQueueLength(true); } + public void testGetQueueLength(boolean fair) { + final PublicSemaphore lock = new PublicSemaphore(1, fair); + assertEquals(0, lock.getQueueLength()); + lock.acquireUninterruptibly(); + Thread t1 = newStartedThread(new InterruptedLockRunnable(lock)); + waitForQueuedThread(lock, t1); + assertEquals(1, lock.getQueueLength()); + Thread t2 = newStartedThread(new InterruptibleLockRunnable(lock)); + waitForQueuedThread(lock, t2); + assertEquals(2, lock.getQueueLength()); + t1.interrupt(); + awaitTermination(t1); + assertEquals(1, lock.getQueueLength()); + lock.release(); + awaitTermination(t2); + assertEquals(0, lock.getQueueLength()); + } + + /** + * getQueuedThreads includes waiting threads + */ + public void testGetQueuedThreads() { testGetQueuedThreads(false); } + public void testGetQueuedThreads_fair() { testGetQueuedThreads(true); } + public void testGetQueuedThreads(boolean fair) { + final PublicSemaphore lock = new PublicSemaphore(1, fair); + assertTrue(lock.getQueuedThreads().isEmpty()); + lock.acquireUninterruptibly(); + assertTrue(lock.getQueuedThreads().isEmpty()); + Thread t1 = newStartedThread(new InterruptedLockRunnable(lock)); + waitForQueuedThread(lock, t1); + assertTrue(lock.getQueuedThreads().contains(t1)); + Thread t2 = newStartedThread(new InterruptibleLockRunnable(lock)); + waitForQueuedThread(lock, t2); + assertTrue(lock.getQueuedThreads().contains(t1)); + assertTrue(lock.getQueuedThreads().contains(t2)); + t1.interrupt(); + awaitTermination(t1); + assertFalse(lock.getQueuedThreads().contains(t1)); + assertTrue(lock.getQueuedThreads().contains(t2)); + lock.release(); + awaitTermination(t2); + assertTrue(lock.getQueuedThreads().isEmpty()); + } + + /** + * drainPermits reports and removes given number of permits + */ + public void testDrainPermits() { testDrainPermits(false); } + public void testDrainPermits_fair() { testDrainPermits(true); } + public void testDrainPermits(boolean fair) { + Semaphore s = new Semaphore(0, fair); + assertEquals(0, s.availablePermits()); + assertEquals(0, s.drainPermits()); + s.release(10); + assertEquals(10, s.availablePermits()); + assertEquals(10, s.drainPermits()); + assertEquals(0, s.availablePermits()); + assertEquals(0, s.drainPermits()); + } + + /** + * release(-N) throws IllegalArgumentException + */ + public void testReleaseIAE() { testReleaseIAE(false); } + public void testReleaseIAE_fair() { testReleaseIAE(true); } + public void testReleaseIAE(boolean fair) { + Semaphore s = new Semaphore(10, fair); + try { + s.release(-1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * reducePermits(-N) throws IllegalArgumentException + */ + public void testReducePermitsIAE() { testReducePermitsIAE(false); } + public void testReducePermitsIAE_fair() { testReducePermitsIAE(true); } + public void testReducePermitsIAE(boolean fair) { + PublicSemaphore s = new PublicSemaphore(10, fair); + try { + s.reducePermits(-1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * reducePermits reduces number of permits + */ + public void testReducePermits() { testReducePermits(false); } + public void testReducePermits_fair() { testReducePermits(true); } + public void testReducePermits(boolean fair) { + PublicSemaphore s = new PublicSemaphore(10, fair); + assertEquals(10, s.availablePermits()); + s.reducePermits(0); + assertEquals(10, s.availablePermits()); + s.reducePermits(1); + assertEquals(9, s.availablePermits()); + s.reducePermits(10); + assertEquals(-1, s.availablePermits()); + s.reducePermits(10); + assertEquals(-11, s.availablePermits()); + s.reducePermits(0); + assertEquals(-11, s.availablePermits()); + } + + /** + * a reserialized semaphore has same number of permits and + * fairness, but no queued threads + */ + public void testSerialization() { testSerialization(false); } + public void testSerialization_fair() { testSerialization(true); } + public void testSerialization(boolean fair) { + try { + Semaphore s = new Semaphore(3, fair); + s.acquire(); + s.acquire(); + s.release(); + + Semaphore clone = serialClone(s); + assertEquals(fair, s.isFair()); + assertEquals(fair, clone.isFair()); + assertEquals(2, s.availablePermits()); + assertEquals(2, clone.availablePermits()); + clone.acquire(); + clone.acquire(); + clone.release(); + assertEquals(2, s.availablePermits()); + assertEquals(1, clone.availablePermits()); + assertFalse(s.hasQueuedThreads()); + assertFalse(clone.hasQueuedThreads()); + } catch (InterruptedException e) { threadUnexpectedException(e); } + + { + PublicSemaphore s = new PublicSemaphore(0, fair); + Thread t = newStartedThread(new InterruptibleLockRunnable(s)); + // waitForQueuedThreads(s); // suffers from "flicker", so ... + waitForQueuedThread(s, t); // ... we use this instead + PublicSemaphore clone = serialClone(s); + assertEquals(fair, s.isFair()); + assertEquals(fair, clone.isFair()); + assertEquals(0, s.availablePermits()); + assertEquals(0, clone.availablePermits()); + assertTrue(s.hasQueuedThreads()); + assertFalse(clone.hasQueuedThreads()); + s.release(); + awaitTermination(t); + assertFalse(s.hasQueuedThreads()); + assertFalse(clone.hasQueuedThreads()); + } + } + + /** + * tryAcquire(n) succeeds when sufficient permits, else fails + */ + public void testTryAcquireNInSameThread() { testTryAcquireNInSameThread(false); } + public void testTryAcquireNInSameThread_fair() { testTryAcquireNInSameThread(true); } + public void testTryAcquireNInSameThread(boolean fair) { + Semaphore s = new Semaphore(2, fair); + assertEquals(2, s.availablePermits()); + assertFalse(s.tryAcquire(3)); + assertEquals(2, s.availablePermits()); + assertTrue(s.tryAcquire(2)); + assertEquals(0, s.availablePermits()); + assertFalse(s.tryAcquire(1)); + assertFalse(s.tryAcquire(2)); + assertEquals(0, s.availablePermits()); + } + + /** + * acquire succeeds if permits available + */ + public void testReleaseAcquireSameThread_acquire() { testReleaseAcquireSameThread(false, AcquireMethod.acquire); } + public void testReleaseAcquireSameThread_acquire_fair() { testReleaseAcquireSameThread(true, AcquireMethod.acquire); } + public void testReleaseAcquireSameThread_acquireN() { testReleaseAcquireSameThread(false, AcquireMethod.acquireN); } + public void testReleaseAcquireSameThread_acquireN_fair() { testReleaseAcquireSameThread(true, AcquireMethod.acquireN); } + public void testReleaseAcquireSameThread_acquireUninterruptibly() { testReleaseAcquireSameThread(false, AcquireMethod.acquireUninterruptibly); } + public void testReleaseAcquireSameThread_acquireUninterruptibly_fair() { testReleaseAcquireSameThread(true, AcquireMethod.acquireUninterruptibly); } + public void testReleaseAcquireSameThread_acquireUninterruptiblyN() { testReleaseAcquireSameThread(false, AcquireMethod.acquireUninterruptibly); } + public void testReleaseAcquireSameThread_acquireUninterruptiblyN_fair() { testReleaseAcquireSameThread(true, AcquireMethod.acquireUninterruptibly); } + public void testReleaseAcquireSameThread_tryAcquire() { testReleaseAcquireSameThread(false, AcquireMethod.tryAcquire); } + public void testReleaseAcquireSameThread_tryAcquire_fair() { testReleaseAcquireSameThread(true, AcquireMethod.tryAcquire); } + public void testReleaseAcquireSameThread_tryAcquireN() { testReleaseAcquireSameThread(false, AcquireMethod.tryAcquireN); } + public void testReleaseAcquireSameThread_tryAcquireN_fair() { testReleaseAcquireSameThread(true, AcquireMethod.tryAcquireN); } + public void testReleaseAcquireSameThread_tryAcquireTimed() { testReleaseAcquireSameThread(false, AcquireMethod.tryAcquireTimed); } + public void testReleaseAcquireSameThread_tryAcquireTimed_fair() { testReleaseAcquireSameThread(true, AcquireMethod.tryAcquireTimed); } + public void testReleaseAcquireSameThread_tryAcquireTimedN() { testReleaseAcquireSameThread(false, AcquireMethod.tryAcquireTimedN); } + public void testReleaseAcquireSameThread_tryAcquireTimedN_fair() { testReleaseAcquireSameThread(true, AcquireMethod.tryAcquireTimedN); } + public void testReleaseAcquireSameThread(boolean fair, + final AcquireMethod acquirer) { + Semaphore s = new Semaphore(1, fair); + for (int i = 1; i < 6; i++) { + s.release(i); + assertEquals(1 + i, s.availablePermits()); + try { + acquirer.acquire(s, i); + } catch (InterruptedException e) { threadUnexpectedException(e); } + assertEquals(1, s.availablePermits()); + } + } + + /** + * release in one thread enables acquire in another thread + */ + public void testReleaseAcquireDifferentThreads_acquire() { testReleaseAcquireDifferentThreads(false, AcquireMethod.acquire); } + public void testReleaseAcquireDifferentThreads_acquire_fair() { testReleaseAcquireDifferentThreads(true, AcquireMethod.acquire); } + public void testReleaseAcquireDifferentThreads_acquireN() { testReleaseAcquireDifferentThreads(false, AcquireMethod.acquireN); } + public void testReleaseAcquireDifferentThreads_acquireN_fair() { testReleaseAcquireDifferentThreads(true, AcquireMethod.acquireN); } + public void testReleaseAcquireDifferentThreads_acquireUninterruptibly() { testReleaseAcquireDifferentThreads(false, AcquireMethod.acquireUninterruptibly); } + public void testReleaseAcquireDifferentThreads_acquireUninterruptibly_fair() { testReleaseAcquireDifferentThreads(true, AcquireMethod.acquireUninterruptibly); } + public void testReleaseAcquireDifferentThreads_acquireUninterruptiblyN() { testReleaseAcquireDifferentThreads(false, AcquireMethod.acquireUninterruptibly); } + public void testReleaseAcquireDifferentThreads_acquireUninterruptiblyN_fair() { testReleaseAcquireDifferentThreads(true, AcquireMethod.acquireUninterruptibly); } + public void testReleaseAcquireDifferentThreads_tryAcquireTimed() { testReleaseAcquireDifferentThreads(false, AcquireMethod.tryAcquireTimed); } + public void testReleaseAcquireDifferentThreads_tryAcquireTimed_fair() { testReleaseAcquireDifferentThreads(true, AcquireMethod.tryAcquireTimed); } + public void testReleaseAcquireDifferentThreads_tryAcquireTimedN() { testReleaseAcquireDifferentThreads(false, AcquireMethod.tryAcquireTimedN); } + public void testReleaseAcquireDifferentThreads_tryAcquireTimedN_fair() { testReleaseAcquireDifferentThreads(true, AcquireMethod.tryAcquireTimedN); } + public void testReleaseAcquireDifferentThreads(boolean fair, + final AcquireMethod acquirer) { + final Semaphore s = new Semaphore(0, fair); + final int rounds = 4; + long startTime = System.nanoTime(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + for (int i = 0; i < rounds; i++) { + assertFalse(s.hasQueuedThreads()); + if (i % 2 == 0) + acquirer.acquire(s); + else + acquirer.acquire(s, 3); + }}}); + + for (int i = 0; i < rounds; i++) { + while (! (s.availablePermits() == 0 && s.hasQueuedThreads())) + Thread.yield(); + assertTrue(t.isAlive()); + if (i % 2 == 0) + s.release(); + else + s.release(3); + } + awaitTermination(t); + assertEquals(0, s.availablePermits()); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + + /** + * fair locks are strictly FIFO + */ + public void testFairLocksFifo() { + final PublicSemaphore s = new PublicSemaphore(1, true); + final CountDownLatch pleaseRelease = new CountDownLatch(1); + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + // Will block; permits are available, but not three + s.acquire(3); + }}); + + waitForQueuedThread(s, t1); + + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + // Will fail, even though 1 permit is available + assertFalse(s.tryAcquire(0L, MILLISECONDS)); + assertFalse(s.tryAcquire(1, 0L, MILLISECONDS)); + + // untimed tryAcquire will barge and succeed + assertTrue(s.tryAcquire()); + s.release(2); + assertTrue(s.tryAcquire(2)); + s.release(); + + pleaseRelease.countDown(); + // Will queue up behind t1, even though 1 permit is available + s.acquire(); + }}); + + await(pleaseRelease); + waitForQueuedThread(s, t2); + s.release(2); + awaitTermination(t1); + assertTrue(t2.isAlive()); + s.release(); + awaitTermination(t2); + } + + /** + * toString indicates current number of permits + */ + public void testToString() { testToString(false); } + public void testToString_fair() { testToString(true); } + public void testToString(boolean fair) { + PublicSemaphore s = new PublicSemaphore(0, fair); + assertTrue(s.toString().contains("Permits = 0")); + s.release(); + assertTrue(s.toString().contains("Permits = 1")); + s.release(2); + assertTrue(s.toString().contains("Permits = 3")); + s.reducePermits(5); + assertTrue(s.toString().contains("Permits = -2")); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/SplittableRandomTest.java b/jdk/test/java/util/concurrent/tck/SplittableRandomTest.java new file mode 100644 index 00000000000..74baf387653 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/SplittableRandomTest.java @@ -0,0 +1,555 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import java.util.SplittableRandom; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.LongAdder; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class SplittableRandomTest extends JSR166TestCase { + + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(SplittableRandomTest.class); + } + + /* + * Testing coverage notes: + * + * 1. Many of the test methods are adapted from ThreadLocalRandomTest. + * + * 2. These tests do not check for random number generator quality. + * But we check for minimal API compliance by requiring that + * repeated calls to nextX methods, up to NCALLS tries, produce at + * least two distinct results. (In some possible universe, a + * "correct" implementation might fail, but the odds are vastly + * less than that of encountering a hardware failure while running + * the test.) For bounded nextX methods, we sample various + * intervals across multiples of primes. In other tests, we repeat + * under REPS different values. + */ + + // max numbers of calls to detect getting stuck on one value + static final int NCALLS = 10000; + + // max sampled int bound + static final int MAX_INT_BOUND = (1 << 26); + + // max sampled long bound + static final long MAX_LONG_BOUND = (1L << 40); + + // Number of replications for other checks + static final int REPS = + Integer.getInteger("SplittableRandomTest.reps", 4); + + /** + * Repeated calls to nextInt produce at least two distinct results + */ + public void testNextInt() { + SplittableRandom sr = new SplittableRandom(); + int f = sr.nextInt(); + int i = 0; + while (i < NCALLS && sr.nextInt() == f) + ++i; + assertTrue(i < NCALLS); + } + + /** + * Repeated calls to nextLong produce at least two distinct results + */ + public void testNextLong() { + SplittableRandom sr = new SplittableRandom(); + long f = sr.nextLong(); + int i = 0; + while (i < NCALLS && sr.nextLong() == f) + ++i; + assertTrue(i < NCALLS); + } + + /** + * Repeated calls to nextDouble produce at least two distinct results + */ + public void testNextDouble() { + SplittableRandom sr = new SplittableRandom(); + double f = sr.nextDouble(); + int i = 0; + while (i < NCALLS && sr.nextDouble() == f) + ++i; + assertTrue(i < NCALLS); + } + + /** + * Two SplittableRandoms created with the same seed produce the + * same values for nextLong. + */ + public void testSeedConstructor() { + for (long seed = 2; seed < MAX_LONG_BOUND; seed += 15485863) { + SplittableRandom sr1 = new SplittableRandom(seed); + SplittableRandom sr2 = new SplittableRandom(seed); + for (int i = 0; i < REPS; ++i) + assertEquals(sr1.nextLong(), sr2.nextLong()); + } + } + + /** + * A SplittableRandom produced by split() of a default-constructed + * SplittableRandom generates a different sequence + */ + public void testSplit1() { + SplittableRandom sr = new SplittableRandom(); + for (int reps = 0; reps < REPS; ++reps) { + SplittableRandom sc = sr.split(); + int i = 0; + while (i < NCALLS && sr.nextLong() == sc.nextLong()) + ++i; + assertTrue(i < NCALLS); + } + } + + /** + * A SplittableRandom produced by split() of a seeded-constructed + * SplittableRandom generates a different sequence + */ + public void testSplit2() { + SplittableRandom sr = new SplittableRandom(12345); + for (int reps = 0; reps < REPS; ++reps) { + SplittableRandom sc = sr.split(); + int i = 0; + while (i < NCALLS && sr.nextLong() == sc.nextLong()) + ++i; + assertTrue(i < NCALLS); + } + } + + /** + * nextInt(non-positive) throws IllegalArgumentException + */ + public void testNextIntBoundNonPositive() { + SplittableRandom sr = new SplittableRandom(); + Runnable[] throwingActions = { + () -> sr.nextInt(-17), + () -> sr.nextInt(0), + () -> sr.nextInt(Integer.MIN_VALUE), + }; + assertThrows(IllegalArgumentException.class, throwingActions); + } + + /** + * nextInt(least >= bound) throws IllegalArgumentException + */ + public void testNextIntBadBounds() { + SplittableRandom sr = new SplittableRandom(); + Runnable[] throwingActions = { + () -> sr.nextInt(17, 2), + () -> sr.nextInt(-42, -42), + () -> sr.nextInt(Integer.MAX_VALUE, Integer.MIN_VALUE), + }; + assertThrows(IllegalArgumentException.class, throwingActions); + } + + /** + * nextInt(bound) returns 0 <= value < bound; + * repeated calls produce at least two distinct results + */ + public void testNextIntBounded() { + SplittableRandom sr = new SplittableRandom(); + for (int i = 0; i < 2; i++) assertEquals(0, sr.nextInt(1)); + // sample bound space across prime number increments + for (int bound = 2; bound < MAX_INT_BOUND; bound += 524959) { + int f = sr.nextInt(bound); + assertTrue(0 <= f && f < bound); + int i = 0; + int j; + while (i < NCALLS && + (j = sr.nextInt(bound)) == f) { + assertTrue(0 <= j && j < bound); + ++i; + } + assertTrue(i < NCALLS); + } + } + + /** + * nextInt(least, bound) returns least <= value < bound; + * repeated calls produce at least two distinct results + */ + public void testNextIntBounded2() { + SplittableRandom sr = new SplittableRandom(); + for (int least = -15485863; least < MAX_INT_BOUND; least += 524959) { + for (int bound = least + 2; bound > least && bound < MAX_INT_BOUND; bound += 49979687) { + int f = sr.nextInt(least, bound); + assertTrue(least <= f && f < bound); + int i = 0; + int j; + while (i < NCALLS && + (j = sr.nextInt(least, bound)) == f) { + assertTrue(least <= j && j < bound); + ++i; + } + assertTrue(i < NCALLS); + } + } + } + + /** + * nextLong(non-positive) throws IllegalArgumentException + */ + public void testNextLongBoundNonPositive() { + SplittableRandom sr = new SplittableRandom(); + Runnable[] throwingActions = { + () -> sr.nextLong(-17L), + () -> sr.nextLong(0L), + () -> sr.nextLong(Long.MIN_VALUE), + }; + assertThrows(IllegalArgumentException.class, throwingActions); + } + + /** + * nextLong(least >= bound) throws IllegalArgumentException + */ + public void testNextLongBadBounds() { + SplittableRandom sr = new SplittableRandom(); + Runnable[] throwingActions = { + () -> sr.nextLong(17L, 2L), + () -> sr.nextLong(-42L, -42L), + () -> sr.nextLong(Long.MAX_VALUE, Long.MIN_VALUE), + }; + assertThrows(IllegalArgumentException.class, throwingActions); + } + + /** + * nextLong(bound) returns 0 <= value < bound; + * repeated calls produce at least two distinct results + */ + public void testNextLongBounded() { + SplittableRandom sr = new SplittableRandom(); + for (int i = 0; i < 2; i++) assertEquals(0L, sr.nextLong(1L)); + for (long bound = 2; bound < MAX_LONG_BOUND; bound += 15485863) { + long f = sr.nextLong(bound); + assertTrue(0 <= f && f < bound); + int i = 0; + long j; + while (i < NCALLS && + (j = sr.nextLong(bound)) == f) { + assertTrue(0 <= j && j < bound); + ++i; + } + assertTrue(i < NCALLS); + } + } + + /** + * nextLong(least, bound) returns least <= value < bound; + * repeated calls produce at least two distinct results + */ + public void testNextLongBounded2() { + SplittableRandom sr = new SplittableRandom(); + for (long least = -86028121; least < MAX_LONG_BOUND; least += 982451653L) { + for (long bound = least + 2; bound > least && bound < MAX_LONG_BOUND; bound += Math.abs(bound * 7919)) { + long f = sr.nextLong(least, bound); + assertTrue(least <= f && f < bound); + int i = 0; + long j; + while (i < NCALLS && + (j = sr.nextLong(least, bound)) == f) { + assertTrue(least <= j && j < bound); + ++i; + } + assertTrue(i < NCALLS); + } + } + } + + /** + * nextDouble(non-positive) throws IllegalArgumentException + */ + public void testNextDoubleBoundNonPositive() { + SplittableRandom sr = new SplittableRandom(); + Runnable[] throwingActions = { + () -> sr.nextDouble(-17.0d), + () -> sr.nextDouble(0.0d), + () -> sr.nextDouble(-Double.MIN_VALUE), + () -> sr.nextDouble(Double.NEGATIVE_INFINITY), + () -> sr.nextDouble(Double.NaN), + }; + assertThrows(IllegalArgumentException.class, throwingActions); + } + + /** + * nextDouble(! (least < bound)) throws IllegalArgumentException + */ + public void testNextDoubleBadBounds() { + SplittableRandom sr = new SplittableRandom(); + Runnable[] throwingActions = { + () -> sr.nextDouble(17.0d, 2.0d), + () -> sr.nextDouble(-42.0d, -42.0d), + () -> sr.nextDouble(Double.MAX_VALUE, Double.MIN_VALUE), + () -> sr.nextDouble(Double.NaN, 0.0d), + () -> sr.nextDouble(0.0d, Double.NaN), + }; + assertThrows(IllegalArgumentException.class, throwingActions); + } + + // TODO: Test infinite bounds! + //() -> sr.nextDouble(Double.NEGATIVE_INFINITY, 0.0d), + //() -> sr.nextDouble(0.0d, Double.POSITIVE_INFINITY), + + /** + * nextDouble(least, bound) returns least <= value < bound; + * repeated calls produce at least two distinct results + */ + public void testNextDoubleBounded2() { + SplittableRandom sr = new SplittableRandom(); + for (double least = 0.0001; least < 1.0e20; least *= 8) { + for (double bound = least * 1.001; bound < 1.0e20; bound *= 16) { + double f = sr.nextDouble(least, bound); + assertTrue(least <= f && f < bound); + int i = 0; + double j; + while (i < NCALLS && + (j = sr.nextDouble(least, bound)) == f) { + assertTrue(least <= j && j < bound); + ++i; + } + assertTrue(i < NCALLS); + } + } + } + + /** + * Invoking sized ints, long, doubles, with negative sizes throws + * IllegalArgumentException + */ + public void testBadStreamSize() { + SplittableRandom r = new SplittableRandom(); + Runnable[] throwingActions = { + () -> { java.util.stream.IntStream x = r.ints(-1L); }, + () -> { java.util.stream.IntStream x = r.ints(-1L, 2, 3); }, + () -> { java.util.stream.LongStream x = r.longs(-1L); }, + () -> { java.util.stream.LongStream x = r.longs(-1L, -1L, 1L); }, + () -> { java.util.stream.DoubleStream x = r.doubles(-1L); }, + () -> { java.util.stream.DoubleStream x = r.doubles(-1L, .5, .6); }, + }; + assertThrows(IllegalArgumentException.class, throwingActions); + } + + /** + * Invoking bounded ints, long, doubles, with illegal bounds throws + * IllegalArgumentException + */ + public void testBadStreamBounds() { + SplittableRandom r = new SplittableRandom(); + Runnable[] throwingActions = { + () -> { java.util.stream.IntStream x = r.ints(2, 1); }, + () -> { java.util.stream.IntStream x = r.ints(10, 42, 42); }, + () -> { java.util.stream.LongStream x = r.longs(-1L, -1L); }, + () -> { java.util.stream.LongStream x = r.longs(10, 1L, -2L); }, + () -> { java.util.stream.DoubleStream x = r.doubles(0.0, 0.0); }, + () -> { java.util.stream.DoubleStream x = r.doubles(10, .5, .4); }, + }; + assertThrows(IllegalArgumentException.class, throwingActions); + } + + /** + * A parallel sized stream of ints generates the given number of values + */ + public void testIntsCount() { + LongAdder counter = new LongAdder(); + SplittableRandom r = new SplittableRandom(); + long size = 0; + for (int reps = 0; reps < REPS; ++reps) { + counter.reset(); + r.ints(size).parallel().forEach(x -> counter.increment()); + assertEquals(size, counter.sum()); + size += 524959; + } + } + + /** + * A parallel sized stream of longs generates the given number of values + */ + public void testLongsCount() { + LongAdder counter = new LongAdder(); + SplittableRandom r = new SplittableRandom(); + long size = 0; + for (int reps = 0; reps < REPS; ++reps) { + counter.reset(); + r.longs(size).parallel().forEach(x -> counter.increment()); + assertEquals(size, counter.sum()); + size += 524959; + } + } + + /** + * A parallel sized stream of doubles generates the given number of values + */ + public void testDoublesCount() { + LongAdder counter = new LongAdder(); + SplittableRandom r = new SplittableRandom(); + long size = 0; + for (int reps = 0; reps < REPS; ++reps) { + counter.reset(); + r.doubles(size).parallel().forEach(x -> counter.increment()); + assertEquals(size, counter.sum()); + size += 524959; + } + } + + /** + * Each of a parallel sized stream of bounded ints is within bounds + */ + public void testBoundedInts() { + AtomicInteger fails = new AtomicInteger(0); + SplittableRandom r = new SplittableRandom(); + long size = 12345L; + for (int least = -15485867; least < MAX_INT_BOUND; least += 524959) { + for (int bound = least + 2; bound > least && bound < MAX_INT_BOUND; bound += 67867967) { + final int lo = least, hi = bound; + r.ints(size, lo, hi).parallel().forEach( + x -> { + if (x < lo || x >= hi) + fails.getAndIncrement(); }); + } + } + assertEquals(0, fails.get()); + } + + /** + * Each of a parallel sized stream of bounded longs is within bounds + */ + public void testBoundedLongs() { + AtomicInteger fails = new AtomicInteger(0); + SplittableRandom r = new SplittableRandom(); + long size = 123L; + for (long least = -86028121; least < MAX_LONG_BOUND; least += 1982451653L) { + for (long bound = least + 2; bound > least && bound < MAX_LONG_BOUND; bound += Math.abs(bound * 7919)) { + final long lo = least, hi = bound; + r.longs(size, lo, hi).parallel().forEach( + x -> { + if (x < lo || x >= hi) + fails.getAndIncrement(); }); + } + } + assertEquals(0, fails.get()); + } + + /** + * Each of a parallel sized stream of bounded doubles is within bounds + */ + public void testBoundedDoubles() { + AtomicInteger fails = new AtomicInteger(0); + SplittableRandom r = new SplittableRandom(); + long size = 456; + for (double least = 0.00011; least < 1.0e20; least *= 9) { + for (double bound = least * 1.0011; bound < 1.0e20; bound *= 17) { + final double lo = least, hi = bound; + r.doubles(size, lo, hi).parallel().forEach( + x -> { + if (x < lo || x >= hi) + fails.getAndIncrement(); }); + } + } + assertEquals(0, fails.get()); + } + + /** + * A parallel unsized stream of ints generates at least 100 values + */ + public void testUnsizedIntsCount() { + LongAdder counter = new LongAdder(); + SplittableRandom r = new SplittableRandom(); + long size = 100; + r.ints().limit(size).parallel().forEach(x -> counter.increment()); + assertEquals(size, counter.sum()); + } + + /** + * A parallel unsized stream of longs generates at least 100 values + */ + public void testUnsizedLongsCount() { + LongAdder counter = new LongAdder(); + SplittableRandom r = new SplittableRandom(); + long size = 100; + r.longs().limit(size).parallel().forEach(x -> counter.increment()); + assertEquals(size, counter.sum()); + } + + /** + * A parallel unsized stream of doubles generates at least 100 values + */ + public void testUnsizedDoublesCount() { + LongAdder counter = new LongAdder(); + SplittableRandom r = new SplittableRandom(); + long size = 100; + r.doubles().limit(size).parallel().forEach(x -> counter.increment()); + assertEquals(size, counter.sum()); + } + + /** + * A sequential unsized stream of ints generates at least 100 values + */ + public void testUnsizedIntsCountSeq() { + LongAdder counter = new LongAdder(); + SplittableRandom r = new SplittableRandom(); + long size = 100; + r.ints().limit(size).forEach(x -> counter.increment()); + assertEquals(size, counter.sum()); + } + + /** + * A sequential unsized stream of longs generates at least 100 values + */ + public void testUnsizedLongsCountSeq() { + LongAdder counter = new LongAdder(); + SplittableRandom r = new SplittableRandom(); + long size = 100; + r.longs().limit(size).forEach(x -> counter.increment()); + assertEquals(size, counter.sum()); + } + + /** + * A sequential unsized stream of doubles generates at least 100 values + */ + public void testUnsizedDoublesCountSeq() { + LongAdder counter = new LongAdder(); + SplittableRandom r = new SplittableRandom(); + long size = 100; + r.doubles().limit(size).forEach(x -> counter.increment()); + assertEquals(size, counter.sum()); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/StampedLockTest.java b/jdk/test/java/util/concurrent/tck/StampedLockTest.java new file mode 100644 index 00000000000..84026409d7b --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/StampedLockTest.java @@ -0,0 +1,906 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea and Martin Buchholz + * with assistance from members of JCP JSR-166 Expert Group and + * released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.StampedLock; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class StampedLockTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(StampedLockTest.class); + } + + /** + * A runnable calling writeLockInterruptibly + */ + class InterruptibleLockRunnable extends CheckedRunnable { + final StampedLock lock; + InterruptibleLockRunnable(StampedLock l) { lock = l; } + public void realRun() throws InterruptedException { + lock.writeLockInterruptibly(); + } + } + + /** + * A runnable calling writeLockInterruptibly that expects to be + * interrupted + */ + class InterruptedLockRunnable extends CheckedInterruptedRunnable { + final StampedLock lock; + InterruptedLockRunnable(StampedLock l) { lock = l; } + public void realRun() throws InterruptedException { + lock.writeLockInterruptibly(); + } + } + + /** + * Releases write lock, checking isWriteLocked before and after + */ + void releaseWriteLock(StampedLock lock, long s) { + assertTrue(lock.isWriteLocked()); + lock.unlockWrite(s); + assertFalse(lock.isWriteLocked()); + } + + /** + * Constructed StampedLock is in unlocked state + */ + public void testConstructor() { + StampedLock lock; + lock = new StampedLock(); + assertFalse(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + } + + /** + * write-locking and read-locking an unlocked lock succeed + */ + public void testLock() { + StampedLock lock = new StampedLock(); + assertFalse(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + long s = lock.writeLock(); + assertTrue(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + lock.unlockWrite(s); + assertFalse(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + long rs = lock.readLock(); + assertFalse(lock.isWriteLocked()); + assertTrue(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 1); + lock.unlockRead(rs); + assertFalse(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + } + + /** + * unlock releases either a read or write lock + */ + public void testUnlock() { + StampedLock lock = new StampedLock(); + assertFalse(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + long s = lock.writeLock(); + assertTrue(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + lock.unlock(s); + assertFalse(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + long rs = lock.readLock(); + assertFalse(lock.isWriteLocked()); + assertTrue(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 1); + lock.unlock(rs); + assertFalse(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + } + + /** + * tryUnlockRead/Write succeeds if locked in associated mode else + * returns false + */ + public void testTryUnlock() { + StampedLock lock = new StampedLock(); + assertFalse(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + long s = lock.writeLock(); + assertTrue(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + assertFalse(lock.tryUnlockRead()); + assertTrue(lock.tryUnlockWrite()); + assertFalse(lock.tryUnlockWrite()); + assertFalse(lock.tryUnlockRead()); + assertFalse(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + long rs = lock.readLock(); + assertFalse(lock.isWriteLocked()); + assertTrue(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 1); + assertFalse(lock.tryUnlockWrite()); + assertTrue(lock.tryUnlockRead()); + assertFalse(lock.tryUnlockRead()); + assertFalse(lock.tryUnlockWrite()); + assertFalse(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(lock.getReadLockCount(), 0); + } + + /** + * write-unlocking an unlocked lock throws IllegalMonitorStateException + */ + public void testWriteUnlock_IMSE() { + StampedLock lock = new StampedLock(); + try { + lock.unlockWrite(0L); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * write-unlocking an unlocked lock throws IllegalMonitorStateException + */ + public void testWriteUnlock_IMSE2() { + StampedLock lock = new StampedLock(); + long s = lock.writeLock(); + lock.unlockWrite(s); + try { + lock.unlockWrite(s); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * write-unlocking after readlock throws IllegalMonitorStateException + */ + public void testWriteUnlock_IMSE3() { + StampedLock lock = new StampedLock(); + long s = lock.readLock(); + try { + lock.unlockWrite(s); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * read-unlocking an unlocked lock throws IllegalMonitorStateException + */ + public void testReadUnlock_IMSE() { + StampedLock lock = new StampedLock(); + long s = lock.readLock(); + lock.unlockRead(s); + try { + lock.unlockRead(s); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * read-unlocking an unlocked lock throws IllegalMonitorStateException + */ + public void testReadUnlock_IMSE2() { + StampedLock lock = new StampedLock(); + try { + lock.unlockRead(0L); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * read-unlocking after writeLock throws IllegalMonitorStateException + */ + public void testReadUnlock_IMSE3() { + StampedLock lock = new StampedLock(); + long s = lock.writeLock(); + try { + lock.unlockRead(s); + shouldThrow(); + } catch (IllegalMonitorStateException success) {} + } + + /** + * validate(0) fails + */ + public void testValidate0() { + StampedLock lock = new StampedLock(); + assertFalse(lock.validate(0L)); + } + + /** + * A stamp obtained from a successful lock operation validates + */ + public void testValidate() throws InterruptedException { + StampedLock lock = new StampedLock(); + long s = lock.writeLock(); + assertTrue(lock.validate(s)); + lock.unlockWrite(s); + s = lock.readLock(); + assertTrue(lock.validate(s)); + lock.unlockRead(s); + assertTrue((s = lock.tryWriteLock()) != 0L); + assertTrue(lock.validate(s)); + lock.unlockWrite(s); + assertTrue((s = lock.tryReadLock()) != 0L); + assertTrue(lock.validate(s)); + lock.unlockRead(s); + assertTrue((s = lock.tryWriteLock(100L, MILLISECONDS)) != 0L); + assertTrue(lock.validate(s)); + lock.unlockWrite(s); + assertTrue((s = lock.tryReadLock(100L, MILLISECONDS)) != 0L); + assertTrue(lock.validate(s)); + lock.unlockRead(s); + assertTrue((s = lock.tryOptimisticRead()) != 0L); + } + + /** + * A stamp obtained from an unsuccessful lock operation does not validate + */ + public void testValidate2() throws InterruptedException { + StampedLock lock = new StampedLock(); + long s; + assertTrue((s = lock.writeLock()) != 0L); + assertTrue(lock.validate(s)); + assertFalse(lock.validate(lock.tryWriteLock())); + assertFalse(lock.validate(lock.tryWriteLock(10L, MILLISECONDS))); + assertFalse(lock.validate(lock.tryReadLock())); + assertFalse(lock.validate(lock.tryReadLock(10L, MILLISECONDS))); + assertFalse(lock.validate(lock.tryOptimisticRead())); + lock.unlockWrite(s); + } + + /** + * writeLockInterruptibly is interruptible + */ + public void testWriteLockInterruptibly_Interruptible() + throws InterruptedException { + final CountDownLatch running = new CountDownLatch(1); + final StampedLock lock = new StampedLock(); + long s = lock.writeLock(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + running.countDown(); + lock.writeLockInterruptibly(); + }}); + + running.await(); + waitForThreadToEnterWaitState(t, 100); + t.interrupt(); + awaitTermination(t); + releaseWriteLock(lock, s); + } + + /** + * timed tryWriteLock is interruptible + */ + public void testWriteTryLock_Interruptible() throws InterruptedException { + final CountDownLatch running = new CountDownLatch(1); + final StampedLock lock = new StampedLock(); + long s = lock.writeLock(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + running.countDown(); + lock.tryWriteLock(2 * LONG_DELAY_MS, MILLISECONDS); + }}); + + running.await(); + waitForThreadToEnterWaitState(t, 100); + t.interrupt(); + awaitTermination(t); + releaseWriteLock(lock, s); + } + + /** + * readLockInterruptibly is interruptible + */ + public void testReadLockInterruptibly_Interruptible() + throws InterruptedException { + final CountDownLatch running = new CountDownLatch(1); + final StampedLock lock = new StampedLock(); + long s = lock.writeLock(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + running.countDown(); + lock.readLockInterruptibly(); + }}); + + running.await(); + waitForThreadToEnterWaitState(t, 100); + t.interrupt(); + awaitTermination(t); + releaseWriteLock(lock, s); + } + + /** + * timed tryReadLock is interruptible + */ + public void testReadTryLock_Interruptible() throws InterruptedException { + final CountDownLatch running = new CountDownLatch(1); + final StampedLock lock = new StampedLock(); + long s = lock.writeLock(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + running.countDown(); + lock.tryReadLock(2 * LONG_DELAY_MS, MILLISECONDS); + }}); + + running.await(); + waitForThreadToEnterWaitState(t, 100); + t.interrupt(); + awaitTermination(t); + releaseWriteLock(lock, s); + } + + /** + * tryWriteLock on an unlocked lock succeeds + */ + public void testWriteTryLock() { + final StampedLock lock = new StampedLock(); + long s = lock.tryWriteLock(); + assertTrue(s != 0L); + assertTrue(lock.isWriteLocked()); + long s2 = lock.tryWriteLock(); + assertEquals(s2, 0L); + releaseWriteLock(lock, s); + } + + /** + * tryWriteLock fails if locked + */ + public void testWriteTryLockWhenLocked() { + final StampedLock lock = new StampedLock(); + long s = lock.writeLock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + long ws = lock.tryWriteLock(); + assertTrue(ws == 0L); + }}); + + awaitTermination(t); + releaseWriteLock(lock, s); + } + + /** + * tryReadLock fails if write-locked + */ + public void testReadTryLockWhenLocked() { + final StampedLock lock = new StampedLock(); + long s = lock.writeLock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + long rs = lock.tryReadLock(); + assertEquals(rs, 0L); + }}); + + awaitTermination(t); + releaseWriteLock(lock, s); + } + + /** + * Multiple threads can hold a read lock when not write-locked + */ + public void testMultipleReadLocks() { + final StampedLock lock = new StampedLock(); + final long s = lock.readLock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long s2 = lock.tryReadLock(); + assertTrue(s2 != 0L); + lock.unlockRead(s2); + long s3 = lock.tryReadLock(LONG_DELAY_MS, MILLISECONDS); + assertTrue(s3 != 0L); + lock.unlockRead(s3); + long s4 = lock.readLock(); + lock.unlockRead(s4); + }}); + + awaitTermination(t); + lock.unlockRead(s); + } + + /** + * A writelock succeeds only after a reading thread unlocks + */ + public void testWriteAfterReadLock() throws InterruptedException { + final CountDownLatch running = new CountDownLatch(1); + final StampedLock lock = new StampedLock(); + long rs = lock.readLock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + running.countDown(); + long s = lock.writeLock(); + lock.unlockWrite(s); + }}); + + running.await(); + waitForThreadToEnterWaitState(t, 100); + assertFalse(lock.isWriteLocked()); + lock.unlockRead(rs); + awaitTermination(t); + assertFalse(lock.isWriteLocked()); + } + + /** + * A writelock succeeds only after reading threads unlock + */ + public void testWriteAfterMultipleReadLocks() { + final StampedLock lock = new StampedLock(); + long s = lock.readLock(); + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() { + long rs = lock.readLock(); + lock.unlockRead(rs); + }}); + + awaitTermination(t1); + + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() { + long ws = lock.writeLock(); + lock.unlockWrite(ws); + }}); + + assertFalse(lock.isWriteLocked()); + lock.unlockRead(s); + awaitTermination(t2); + assertFalse(lock.isWriteLocked()); + } + + /** + * Readlocks succeed only after a writing thread unlocks + */ + public void testReadAfterWriteLock() { + final StampedLock lock = new StampedLock(); + final long s = lock.writeLock(); + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() { + long rs = lock.readLock(); + lock.unlockRead(rs); + }}); + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() { + long rs = lock.readLock(); + lock.unlockRead(rs); + }}); + + releaseWriteLock(lock, s); + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * tryReadLock succeeds if readlocked but not writelocked + */ + public void testTryLockWhenReadLocked() { + final StampedLock lock = new StampedLock(); + long s = lock.readLock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + long rs = lock.tryReadLock(); + threadAssertTrue(rs != 0L); + lock.unlockRead(rs); + }}); + + awaitTermination(t); + lock.unlockRead(s); + } + + /** + * tryWriteLock fails when readlocked + */ + public void testWriteTryLockWhenReadLocked() { + final StampedLock lock = new StampedLock(); + long s = lock.readLock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() { + long ws = lock.tryWriteLock(); + threadAssertEquals(ws, 0L); + }}); + + awaitTermination(t); + lock.unlockRead(s); + } + + /** + * timed tryWriteLock times out if locked + */ + public void testWriteTryLock_Timeout() { + final StampedLock lock = new StampedLock(); + long s = lock.writeLock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + long timeoutMillis = 10; + long ws = lock.tryWriteLock(timeoutMillis, MILLISECONDS); + assertEquals(ws, 0L); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + }}); + + awaitTermination(t); + releaseWriteLock(lock, s); + } + + /** + * timed tryReadLock times out if write-locked + */ + public void testReadTryLock_Timeout() { + final StampedLock lock = new StampedLock(); + long s = lock.writeLock(); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + long timeoutMillis = 10; + long rs = lock.tryReadLock(timeoutMillis, MILLISECONDS); + assertEquals(rs, 0L); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + }}); + + awaitTermination(t); + assertTrue(lock.isWriteLocked()); + lock.unlockWrite(s); + } + + /** + * writeLockInterruptibly succeeds if unlocked, else is interruptible + */ + public void testWriteLockInterruptibly() throws InterruptedException { + final CountDownLatch running = new CountDownLatch(1); + final StampedLock lock = new StampedLock(); + long s = lock.writeLockInterruptibly(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + running.countDown(); + lock.writeLockInterruptibly(); + }}); + + running.await(); + waitForThreadToEnterWaitState(t, 100); + t.interrupt(); + assertTrue(lock.isWriteLocked()); + awaitTermination(t); + releaseWriteLock(lock, s); + } + + /** + * readLockInterruptibly succeeds if lock free else is interruptible + */ + public void testReadLockInterruptibly() throws InterruptedException { + final CountDownLatch running = new CountDownLatch(1); + final StampedLock lock = new StampedLock(); + long s; + s = lock.readLockInterruptibly(); + lock.unlockRead(s); + s = lock.writeLockInterruptibly(); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + running.countDown(); + lock.readLockInterruptibly(); + }}); + + running.await(); + waitForThreadToEnterWaitState(t, 100); + t.interrupt(); + awaitTermination(t); + releaseWriteLock(lock, s); + } + + /** + * A serialized lock deserializes as unlocked + */ + public void testSerialization() { + StampedLock lock = new StampedLock(); + lock.writeLock(); + StampedLock clone = serialClone(lock); + assertTrue(lock.isWriteLocked()); + assertFalse(clone.isWriteLocked()); + long s = clone.writeLock(); + assertTrue(clone.isWriteLocked()); + clone.unlockWrite(s); + assertFalse(clone.isWriteLocked()); + } + + /** + * toString indicates current lock state + */ + public void testToString() { + StampedLock lock = new StampedLock(); + assertTrue(lock.toString().contains("Unlocked")); + long s = lock.writeLock(); + assertTrue(lock.toString().contains("Write-locked")); + lock.unlockWrite(s); + s = lock.readLock(); + assertTrue(lock.toString().contains("Read-locks")); + } + + /** + * tryOptimisticRead succeeds and validates if unlocked, fails if locked + */ + public void testValidateOptimistic() throws InterruptedException { + StampedLock lock = new StampedLock(); + long s, p; + assertTrue((p = lock.tryOptimisticRead()) != 0L); + assertTrue(lock.validate(p)); + assertTrue((s = lock.writeLock()) != 0L); + assertFalse((p = lock.tryOptimisticRead()) != 0L); + assertTrue(lock.validate(s)); + lock.unlockWrite(s); + assertTrue((p = lock.tryOptimisticRead()) != 0L); + assertTrue(lock.validate(p)); + assertTrue((s = lock.readLock()) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryOptimisticRead()) != 0L); + assertTrue(lock.validate(p)); + lock.unlockRead(s); + assertTrue((s = lock.tryWriteLock()) != 0L); + assertTrue(lock.validate(s)); + assertFalse((p = lock.tryOptimisticRead()) != 0L); + lock.unlockWrite(s); + assertTrue((s = lock.tryReadLock()) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryOptimisticRead()) != 0L); + lock.unlockRead(s); + assertTrue(lock.validate(p)); + assertTrue((s = lock.tryWriteLock(100L, MILLISECONDS)) != 0L); + assertFalse((p = lock.tryOptimisticRead()) != 0L); + assertTrue(lock.validate(s)); + lock.unlockWrite(s); + assertTrue((s = lock.tryReadLock(100L, MILLISECONDS)) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryOptimisticRead()) != 0L); + lock.unlockRead(s); + assertTrue((p = lock.tryOptimisticRead()) != 0L); + } + + /** + * tryOptimisticRead stamp does not validate if a write lock intervenes + */ + public void testValidateOptimisticWriteLocked() { + StampedLock lock = new StampedLock(); + long s, p; + assertTrue((p = lock.tryOptimisticRead()) != 0L); + assertTrue((s = lock.writeLock()) != 0L); + assertFalse(lock.validate(p)); + assertFalse((p = lock.tryOptimisticRead()) != 0L); + assertTrue(lock.validate(s)); + lock.unlockWrite(s); + } + + /** + * tryOptimisticRead stamp does not validate if a write lock + * intervenes in another thread + */ + public void testValidateOptimisticWriteLocked2() + throws InterruptedException { + final CountDownLatch running = new CountDownLatch(1); + final StampedLock lock = new StampedLock(); + long s, p; + assertTrue((p = lock.tryOptimisticRead()) != 0L); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + lock.writeLockInterruptibly(); + running.countDown(); + lock.writeLockInterruptibly(); + }}); + + running.await(); + assertFalse(lock.validate(p)); + assertFalse((p = lock.tryOptimisticRead()) != 0L); + t.interrupt(); + awaitTermination(t); + } + + /** + * tryConvertToOptimisticRead succeeds and validates if successfully locked, + */ + public void testTryConvertToOptimisticRead() throws InterruptedException { + StampedLock lock = new StampedLock(); + long s, p; + s = 0L; + assertFalse((p = lock.tryConvertToOptimisticRead(s)) != 0L); + assertTrue((s = lock.tryOptimisticRead()) != 0L); + assertTrue((p = lock.tryConvertToOptimisticRead(s)) != 0L); + assertTrue((s = lock.writeLock()) != 0L); + assertTrue((p = lock.tryConvertToOptimisticRead(s)) != 0L); + assertTrue(lock.validate(p)); + assertTrue((s = lock.readLock()) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryConvertToOptimisticRead(s)) != 0L); + assertTrue(lock.validate(p)); + assertTrue((s = lock.tryWriteLock()) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryConvertToOptimisticRead(s)) != 0L); + assertTrue(lock.validate(p)); + assertTrue((s = lock.tryReadLock()) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryConvertToOptimisticRead(s)) != 0L); + assertTrue(lock.validate(p)); + assertTrue((s = lock.tryWriteLock(100L, MILLISECONDS)) != 0L); + assertTrue((p = lock.tryConvertToOptimisticRead(s)) != 0L); + assertTrue(lock.validate(p)); + assertTrue((s = lock.tryReadLock(100L, MILLISECONDS)) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryConvertToOptimisticRead(s)) != 0L); + assertTrue(lock.validate(p)); + } + + /** + * tryConvertToReadLock succeeds and validates if successfully locked + * or lock free; + */ + public void testTryConvertToReadLock() throws InterruptedException { + StampedLock lock = new StampedLock(); + long s, p; + s = 0L; + assertFalse((p = lock.tryConvertToReadLock(s)) != 0L); + assertTrue((s = lock.tryOptimisticRead()) != 0L); + assertTrue((p = lock.tryConvertToReadLock(s)) != 0L); + lock.unlockRead(p); + assertTrue((s = lock.writeLock()) != 0L); + assertTrue((p = lock.tryConvertToReadLock(s)) != 0L); + assertTrue(lock.validate(p)); + lock.unlockRead(p); + assertTrue((s = lock.readLock()) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryConvertToReadLock(s)) != 0L); + assertTrue(lock.validate(p)); + lock.unlockRead(p); + assertTrue((s = lock.tryWriteLock()) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryConvertToReadLock(s)) != 0L); + assertTrue(lock.validate(p)); + lock.unlockRead(p); + assertTrue((s = lock.tryReadLock()) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryConvertToReadLock(s)) != 0L); + assertTrue(lock.validate(p)); + lock.unlockRead(p); + assertTrue((s = lock.tryWriteLock(100L, MILLISECONDS)) != 0L); + assertTrue((p = lock.tryConvertToReadLock(s)) != 0L); + assertTrue(lock.validate(p)); + lock.unlockRead(p); + assertTrue((s = lock.tryReadLock(100L, MILLISECONDS)) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryConvertToReadLock(s)) != 0L); + assertTrue(lock.validate(p)); + lock.unlockRead(p); + } + + /** + * tryConvertToWriteLock succeeds and validates if successfully locked + * or lock free; + */ + public void testTryConvertToWriteLock() throws InterruptedException { + StampedLock lock = new StampedLock(); + long s, p; + s = 0L; + assertFalse((p = lock.tryConvertToWriteLock(s)) != 0L); + assertTrue((s = lock.tryOptimisticRead()) != 0L); + assertTrue((p = lock.tryConvertToWriteLock(s)) != 0L); + lock.unlockWrite(p); + assertTrue((s = lock.writeLock()) != 0L); + assertTrue((p = lock.tryConvertToWriteLock(s)) != 0L); + assertTrue(lock.validate(p)); + lock.unlockWrite(p); + assertTrue((s = lock.readLock()) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryConvertToWriteLock(s)) != 0L); + assertTrue(lock.validate(p)); + lock.unlockWrite(p); + assertTrue((s = lock.tryWriteLock()) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryConvertToWriteLock(s)) != 0L); + assertTrue(lock.validate(p)); + lock.unlockWrite(p); + assertTrue((s = lock.tryReadLock()) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryConvertToWriteLock(s)) != 0L); + assertTrue(lock.validate(p)); + lock.unlockWrite(p); + assertTrue((s = lock.tryWriteLock(100L, MILLISECONDS)) != 0L); + assertTrue((p = lock.tryConvertToWriteLock(s)) != 0L); + assertTrue(lock.validate(p)); + lock.unlockWrite(p); + assertTrue((s = lock.tryReadLock(100L, MILLISECONDS)) != 0L); + assertTrue(lock.validate(s)); + assertTrue((p = lock.tryConvertToWriteLock(s)) != 0L); + assertTrue(lock.validate(p)); + lock.unlockWrite(p); + } + + /** + * asWriteLock can be locked and unlocked + */ + public void testAsWriteLock() { + StampedLock sl = new StampedLock(); + Lock lock = sl.asWriteLock(); + lock.lock(); + assertFalse(lock.tryLock()); + lock.unlock(); + assertTrue(lock.tryLock()); + } + + /** + * asReadLock can be locked and unlocked + */ + public void testAsReadLock() { + StampedLock sl = new StampedLock(); + Lock lock = sl.asReadLock(); + lock.lock(); + lock.unlock(); + assertTrue(lock.tryLock()); + } + + /** + * asReadWriteLock.writeLock can be locked and unlocked + */ + public void testAsReadWriteLockWriteLock() { + StampedLock sl = new StampedLock(); + Lock lock = sl.asReadWriteLock().writeLock(); + lock.lock(); + assertFalse(lock.tryLock()); + lock.unlock(); + assertTrue(lock.tryLock()); + } + + /** + * asReadWriteLock.readLock can be locked and unlocked + */ + public void testAsReadWriteLockReadLock() { + StampedLock sl = new StampedLock(); + Lock lock = sl.asReadWriteLock().readLock(); + lock.lock(); + lock.unlock(); + assertTrue(lock.tryLock()); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/SubmissionPublisherTest.java b/jdk/test/java/util/concurrent/tck/SubmissionPublisherTest.java new file mode 100644 index 00000000000..ab4c9e90438 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/SubmissionPublisherTest.java @@ -0,0 +1,1010 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea and Martin Buchholz with assistance from + * members of JCP JSR-166 Expert Group and released to the public + * domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.Flow; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.SubmissionPublisher; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; +import java.util.stream.Stream; +import junit.framework.Test; +import junit.framework.TestSuite; + +import static java.util.concurrent.Flow.Publisher; +import static java.util.concurrent.Flow.Subscriber; +import static java.util.concurrent.Flow.Subscription; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; + +public class SubmissionPublisherTest extends JSR166TestCase { + + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(SubmissionPublisherTest.class); + } + + final Executor basicExecutor = basicPublisher().getExecutor(); + + static SubmissionPublisher basicPublisher() { + return new SubmissionPublisher(); + } + + static class SPException extends RuntimeException {} + + class TestSubscriber implements Subscriber { + volatile Subscription sn; + int last; // Requires that onNexts are in numeric order + volatile int nexts; + volatile int errors; + volatile int completes; + volatile boolean throwOnCall = false; + volatile boolean request = true; + volatile Throwable lastError; + + public synchronized void onSubscribe(Subscription s) { + threadAssertTrue(sn == null); + sn = s; + notifyAll(); + if (throwOnCall) + throw new SPException(); + if (request) + sn.request(1L); + } + public synchronized void onNext(Integer t) { + ++nexts; + notifyAll(); + int current = t.intValue(); + threadAssertTrue(current >= last); + last = current; + if (request) + sn.request(1L); + if (throwOnCall) + throw new SPException(); + } + public synchronized void onError(Throwable t) { + threadAssertTrue(completes == 0); + threadAssertTrue(errors == 0); + lastError = t; + ++errors; + notifyAll(); + } + public synchronized void onComplete() { + threadAssertTrue(completes == 0); + ++completes; + notifyAll(); + } + + synchronized void awaitSubscribe() { + while (sn == null) { + try { + wait(); + } catch (Exception ex) { + threadUnexpectedException(ex); + break; + } + } + } + synchronized void awaitNext(int n) { + while (nexts < n) { + try { + wait(); + } catch (Exception ex) { + threadUnexpectedException(ex); + break; + } + } + } + synchronized void awaitComplete() { + while (completes == 0 && errors == 0) { + try { + wait(); + } catch (Exception ex) { + threadUnexpectedException(ex); + break; + } + } + } + synchronized void awaitError() { + while (errors == 0) { + try { + wait(); + } catch (Exception ex) { + threadUnexpectedException(ex); + break; + } + } + } + + } + + /** + * A new SubmissionPublisher has no subscribers, a non-null + * executor, a power-of-two capacity, is not closed, and reports + * zero demand and lag + */ + void checkInitialState(SubmissionPublisher p) { + assertFalse(p.hasSubscribers()); + assertEquals(0, p.getNumberOfSubscribers()); + assertTrue(p.getSubscribers().isEmpty()); + assertFalse(p.isClosed()); + assertNull(p.getClosedException()); + int n = p.getMaxBufferCapacity(); + assertTrue((n & (n - 1)) == 0); // power of two + assertNotNull(p.getExecutor()); + assertEquals(0, p.estimateMinimumDemand()); + assertEquals(0, p.estimateMaximumLag()); + } + + /** + * A default-constructed SubmissionPublisher has no subscribers, + * is not closed, has default buffer size, and uses the + * defaultExecutor + */ + public void testConstructor1() { + SubmissionPublisher p = new SubmissionPublisher(); + checkInitialState(p); + assertEquals(p.getMaxBufferCapacity(), Flow.defaultBufferSize()); + Executor e = p.getExecutor(), c = ForkJoinPool.commonPool(); + if (ForkJoinPool.getCommonPoolParallelism() > 1) + assertSame(e, c); + else + assertNotSame(e, c); + } + + /** + * A new SubmissionPublisher has no subscribers, is not closed, + * has the given buffer size, and uses the given executor + */ + public void testConstructor2() { + Executor e = Executors.newFixedThreadPool(1); + SubmissionPublisher p = new SubmissionPublisher(e, 8); + checkInitialState(p); + assertSame(p.getExecutor(), e); + assertEquals(8, p.getMaxBufferCapacity()); + } + + /** + * A null Executor argument to SubmissionPublisher constructor throws NPE + */ + public void testConstructor3() { + try { + new SubmissionPublisher(null, 8); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * A negative capacity argument to SubmissionPublisher constructor + * throws IAE + */ + public void testConstructor4() { + Executor e = Executors.newFixedThreadPool(1); + try { + new SubmissionPublisher(e, -1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * A closed publisher reports isClosed with no closedException and + * throws ISE upon attempted submission; a subsequent close or + * closeExceptionally has no additional effect. + */ + public void testClose() { + SubmissionPublisher p = basicPublisher(); + checkInitialState(p); + p.close(); + assertTrue(p.isClosed()); + assertNull(p.getClosedException()); + try { + p.submit(1); + shouldThrow(); + } catch (IllegalStateException success) {} + Throwable ex = new SPException(); + p.closeExceptionally(ex); + assertTrue(p.isClosed()); + assertNull(p.getClosedException()); + } + + /** + * A publisher closedExceptionally reports isClosed with the + * closedException and throws ISE upon attempted submission; a + * subsequent close or closeExceptionally has no additional + * effect. + */ + public void testCloseExceptionally() { + SubmissionPublisher p = basicPublisher(); + checkInitialState(p); + Throwable ex = new SPException(); + p.closeExceptionally(ex); + assertTrue(p.isClosed()); + assertSame(p.getClosedException(), ex); + try { + p.submit(1); + shouldThrow(); + } catch (IllegalStateException success) {} + p.close(); + assertTrue(p.isClosed()); + assertSame(p.getClosedException(), ex); + } + + /** + * Upon subscription, the subscriber's onSubscribe is called, no + * other Subscriber methods are invoked, the publisher + * hasSubscribers, isSubscribed is true, and existing + * subscriptions are unaffected. + */ + public void testSubscribe1() { + TestSubscriber s = new TestSubscriber(); + SubmissionPublisher p = basicPublisher(); + p.subscribe(s); + assertTrue(p.hasSubscribers()); + assertEquals(1, p.getNumberOfSubscribers()); + assertTrue(p.getSubscribers().contains(s)); + assertTrue(p.isSubscribed(s)); + s.awaitSubscribe(); + assertNotNull(s.sn); + assertEquals(0, s.nexts); + assertEquals(0, s.errors); + assertEquals(0, s.completes); + TestSubscriber s2 = new TestSubscriber(); + p.subscribe(s2); + assertTrue(p.hasSubscribers()); + assertEquals(2, p.getNumberOfSubscribers()); + assertTrue(p.getSubscribers().contains(s)); + assertTrue(p.getSubscribers().contains(s2)); + assertTrue(p.isSubscribed(s)); + assertTrue(p.isSubscribed(s2)); + s2.awaitSubscribe(); + assertNotNull(s2.sn); + assertEquals(0, s2.nexts); + assertEquals(0, s2.errors); + assertEquals(0, s2.completes); + p.close(); + } + + /** + * If closed, upon subscription, the subscriber's onComplete + * method is invoked + */ + public void testSubscribe2() { + TestSubscriber s = new TestSubscriber(); + SubmissionPublisher p = basicPublisher(); + p.close(); + p.subscribe(s); + s.awaitComplete(); + assertEquals(0, s.nexts); + assertEquals(0, s.errors); + assertEquals(1, s.completes, 1); + } + + /** + * If closedExceptionally, upon subscription, the subscriber's + * onError method is invoked + */ + public void testSubscribe3() { + TestSubscriber s = new TestSubscriber(); + SubmissionPublisher p = basicPublisher(); + Throwable ex = new SPException(); + p.closeExceptionally(ex); + assertTrue(p.isClosed()); + assertSame(p.getClosedException(), ex); + p.subscribe(s); + s.awaitError(); + assertEquals(0, s.nexts); + assertEquals(1, s.errors); + } + + /** + * Upon attempted resubscription, the subscriber's onError is + * called and the subscription is cancelled. + */ + public void testSubscribe4() { + TestSubscriber s = new TestSubscriber(); + SubmissionPublisher p = basicPublisher(); + p.subscribe(s); + assertTrue(p.hasSubscribers()); + assertEquals(1, p.getNumberOfSubscribers()); + assertTrue(p.getSubscribers().contains(s)); + assertTrue(p.isSubscribed(s)); + s.awaitSubscribe(); + assertNotNull(s.sn); + assertEquals(0, s.nexts); + assertEquals(0, s.errors); + assertEquals(0, s.completes); + p.subscribe(s); + s.awaitError(); + assertEquals(0, s.nexts); + assertEquals(1, s.errors); + assertFalse(p.isSubscribed(s)); + } + + /** + * An exception thrown in onSubscribe causes onError + */ + public void testSubscribe5() { + TestSubscriber s = new TestSubscriber(); + SubmissionPublisher p = basicPublisher(); + s.throwOnCall = true; + try { + p.subscribe(s); + } catch (Exception ok) {} + s.awaitError(); + assertEquals(0, s.nexts); + assertEquals(1, s.errors); + assertEquals(0, s.completes); + } + + /** + * subscribe(null) throws NPE + */ + public void testSubscribe6() { + SubmissionPublisher p = basicPublisher(); + try { + p.subscribe(null); + shouldThrow(); + } catch (NullPointerException success) {} + checkInitialState(p); + } + + /** + * Closing a publisher causes onComplete to subscribers + */ + public void testCloseCompletes() { + SubmissionPublisher p = basicPublisher(); + TestSubscriber s1 = new TestSubscriber(); + TestSubscriber s2 = new TestSubscriber(); + p.subscribe(s1); + p.subscribe(s2); + p.submit(1); + p.close(); + assertTrue(p.isClosed()); + assertNull(p.getClosedException()); + s1.awaitComplete(); + assertEquals(1, s1.nexts); + assertEquals(1, s1.completes); + s2.awaitComplete(); + assertEquals(1, s2.nexts); + assertEquals(1, s2.completes); + } + + /** + * Closing a publisher exceptionally causes onError to subscribers + */ + public void testCloseExceptionallyError() { + SubmissionPublisher p = basicPublisher(); + TestSubscriber s1 = new TestSubscriber(); + TestSubscriber s2 = new TestSubscriber(); + p.subscribe(s1); + p.subscribe(s2); + p.submit(1); + p.closeExceptionally(new SPException()); + assertTrue(p.isClosed()); + s1.awaitError(); + assertTrue(s1.nexts <= 1); + assertEquals(1, s1.errors); + s2.awaitError(); + assertTrue(s2.nexts <= 1); + assertEquals(1, s2.errors); + } + + /** + * Cancelling a subscription eventually causes no more onNexts to be issued + */ + public void testCancel() { + SubmissionPublisher p = basicPublisher(); + TestSubscriber s1 = new TestSubscriber(); + TestSubscriber s2 = new TestSubscriber(); + p.subscribe(s1); + p.subscribe(s2); + s1.awaitSubscribe(); + p.submit(1); + s1.sn.cancel(); + for (int i = 2; i <= 20; ++i) + p.submit(i); + p.close(); + s2.awaitComplete(); + assertEquals(20, s2.nexts); + assertEquals(1, s2.completes); + assertTrue(s1.nexts < 20); + assertFalse(p.isSubscribed(s1)); + } + + /** + * Throwing an exception in onNext causes onError + */ + public void testThrowOnNext() { + SubmissionPublisher p = basicPublisher(); + TestSubscriber s1 = new TestSubscriber(); + TestSubscriber s2 = new TestSubscriber(); + p.subscribe(s1); + p.subscribe(s2); + s1.awaitSubscribe(); + p.submit(1); + s1.throwOnCall = true; + p.submit(2); + p.close(); + s2.awaitComplete(); + assertEquals(2, s2.nexts); + s1.awaitComplete(); + assertEquals(1, s1.errors); + } + + /** + * If a handler is supplied in constructor, it is invoked when + * subscriber throws an exception in onNext + */ + public void testThrowOnNextHandler() { + AtomicInteger calls = new AtomicInteger(); + SubmissionPublisher p = new SubmissionPublisher + (basicExecutor, 8, + (s, e) -> calls.getAndIncrement()); + TestSubscriber s1 = new TestSubscriber(); + TestSubscriber s2 = new TestSubscriber(); + p.subscribe(s1); + p.subscribe(s2); + s1.awaitSubscribe(); + p.submit(1); + s1.throwOnCall = true; + p.submit(2); + p.close(); + s2.awaitComplete(); + assertEquals(2, s2.nexts); + assertEquals(1, s2.completes); + s1.awaitError(); + assertEquals(1, s1.errors); + assertEquals(1, calls.get()); + } + + /** + * onNext items are issued in the same order to each subscriber + */ + public void testOrder() { + SubmissionPublisher p = basicPublisher(); + TestSubscriber s1 = new TestSubscriber(); + TestSubscriber s2 = new TestSubscriber(); + p.subscribe(s1); + p.subscribe(s2); + for (int i = 1; i <= 20; ++i) + p.submit(i); + p.close(); + s2.awaitComplete(); + s1.awaitComplete(); + assertEquals(20, s2.nexts); + assertEquals(1, s2.completes); + assertEquals(20, s1.nexts); + assertEquals(1, s1.completes); + } + + /** + * onNext is issued only if requested + */ + public void testRequest1() { + SubmissionPublisher p = basicPublisher(); + TestSubscriber s1 = new TestSubscriber(); + s1.request = false; + p.subscribe(s1); + s1.awaitSubscribe(); + assertTrue(p.estimateMinimumDemand() == 0); + TestSubscriber s2 = new TestSubscriber(); + p.subscribe(s2); + p.submit(1); + p.submit(2); + s2.awaitNext(1); + assertEquals(0, s1.nexts); + s1.sn.request(3); + p.submit(3); + p.close(); + s2.awaitComplete(); + assertEquals(3, s2.nexts); + assertEquals(1, s2.completes); + s1.awaitComplete(); + assertTrue(s1.nexts > 0); + assertEquals(1, s1.completes); + } + + /** + * onNext is not issued when requests become zero + */ + public void testRequest2() { + SubmissionPublisher p = basicPublisher(); + TestSubscriber s1 = new TestSubscriber(); + TestSubscriber s2 = new TestSubscriber(); + p.subscribe(s1); + p.subscribe(s2); + s2.awaitSubscribe(); + s1.awaitSubscribe(); + s1.request = false; + p.submit(1); + p.submit(2); + p.close(); + s2.awaitComplete(); + assertEquals(2, s2.nexts); + assertEquals(1, s2.completes); + s1.awaitNext(1); + assertEquals(1, s1.nexts); + } + + /** + * Negative request causes error + */ + public void testRequest3() { + SubmissionPublisher p = basicPublisher(); + TestSubscriber s1 = new TestSubscriber(); + TestSubscriber s2 = new TestSubscriber(); + p.subscribe(s1); + p.subscribe(s2); + s2.awaitSubscribe(); + s1.awaitSubscribe(); + s1.sn.request(-1L); + p.submit(1); + p.submit(2); + p.close(); + s2.awaitComplete(); + assertEquals(2, s2.nexts); + assertEquals(1, s2.completes); + s1.awaitError(); + assertEquals(1, s1.errors); + assertTrue(s1.lastError instanceof IllegalArgumentException); + } + + /** + * estimateMinimumDemand reports 0 until request, nonzero after + * request, and zero again after delivery + */ + public void testEstimateMinimumDemand() { + TestSubscriber s = new TestSubscriber(); + SubmissionPublisher p = basicPublisher(); + s.request = false; + p.subscribe(s); + s.awaitSubscribe(); + assertEquals(0, p.estimateMinimumDemand()); + s.sn.request(1); + assertEquals(1, p.estimateMinimumDemand()); + p.submit(1); + s.awaitNext(1); + assertEquals(0, p.estimateMinimumDemand()); + } + + /** + * submit to a publisher with no subscribers returns lag 0 + */ + public void testEmptySubmit() { + SubmissionPublisher p = basicPublisher(); + assertEquals(0, p.submit(1)); + } + + /** + * submit(null) throws NPE + */ + public void testNullSubmit() { + SubmissionPublisher p = basicPublisher(); + try { + p.submit(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * submit returns number of lagged items, compatible with result + * of estimateMaximumLag. + */ + public void testLaggedSubmit() { + SubmissionPublisher p = basicPublisher(); + TestSubscriber s1 = new TestSubscriber(); + s1.request = false; + TestSubscriber s2 = new TestSubscriber(); + s2.request = false; + p.subscribe(s1); + p.subscribe(s2); + s2.awaitSubscribe(); + s1.awaitSubscribe(); + assertEquals(1, p.submit(1)); + assertTrue(p.estimateMaximumLag() >= 1); + assertTrue(p.submit(2) >= 2); + assertTrue(p.estimateMaximumLag() >= 2); + s1.sn.request(4); + assertTrue(p.submit(3) >= 3); + assertTrue(p.estimateMaximumLag() >= 3); + s2.sn.request(4); + p.submit(4); + p.close(); + s2.awaitComplete(); + assertEquals(4, s2.nexts); + s1.awaitComplete(); + assertEquals(4, s2.nexts); + } + + /** + * submit eventually issues requested items when buffer capacity is 1 + */ + public void testCap1Submit() { + SubmissionPublisher p = new SubmissionPublisher( + basicExecutor, 1); + TestSubscriber s1 = new TestSubscriber(); + TestSubscriber s2 = new TestSubscriber(); + p.subscribe(s1); + p.subscribe(s2); + for (int i = 1; i <= 20; ++i) { + assertTrue(p.estimateMinimumDemand() <= 1); + assertTrue(p.submit(i) >= 0); + } + p.close(); + s2.awaitComplete(); + s1.awaitComplete(); + assertEquals(20, s2.nexts); + assertEquals(1, s2.completes); + assertEquals(20, s1.nexts); + assertEquals(1, s1.completes); + } + + static boolean noopHandle(AtomicInteger count) { + count.getAndIncrement(); + return false; + } + + static boolean reqHandle(AtomicInteger count, Subscriber s) { + count.getAndIncrement(); + ((TestSubscriber)s).sn.request(Long.MAX_VALUE); + return true; + } + + /** + * offer to a publisher with no subscribers returns lag 0 + */ + public void testEmptyOffer() { + SubmissionPublisher p = basicPublisher(); + assertEquals(0, p.offer(1, null)); + } + + /** + * offer(null) throws NPE + */ + public void testNullOffer() { + SubmissionPublisher p = basicPublisher(); + try { + p.offer(null, null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * offer returns number of lagged items if not saturated + */ + public void testLaggedOffer() { + SubmissionPublisher p = basicPublisher(); + TestSubscriber s1 = new TestSubscriber(); + s1.request = false; + TestSubscriber s2 = new TestSubscriber(); + s2.request = false; + p.subscribe(s1); + p.subscribe(s2); + s2.awaitSubscribe(); + s1.awaitSubscribe(); + assertTrue(p.offer(1, null) >= 1); + assertTrue(p.offer(2, null) >= 2); + s1.sn.request(4); + assertTrue(p.offer(3, null) >= 3); + s2.sn.request(4); + p.offer(4, null); + p.close(); + s2.awaitComplete(); + assertEquals(4, s2.nexts); + s1.awaitComplete(); + assertEquals(4, s2.nexts); + } + + /** + * offer reports drops if saturated + */ + public void testDroppedOffer() { + SubmissionPublisher p = new SubmissionPublisher( + basicExecutor, 4); + TestSubscriber s1 = new TestSubscriber(); + s1.request = false; + TestSubscriber s2 = new TestSubscriber(); + s2.request = false; + p.subscribe(s1); + p.subscribe(s2); + s2.awaitSubscribe(); + s1.awaitSubscribe(); + for (int i = 1; i <= 4; ++i) + assertTrue(p.offer(i, null) >= 0); + p.offer(5, null); + assertTrue(p.offer(6, null) < 0); + s1.sn.request(64); + assertTrue(p.offer(7, null) < 0); + s2.sn.request(64); + p.close(); + s2.awaitComplete(); + assertTrue(s2.nexts >= 4); + s1.awaitComplete(); + assertTrue(s1.nexts >= 4); + } + + /** + * offer invokes drop handler if saturated + */ + public void testHandledDroppedOffer() { + AtomicInteger calls = new AtomicInteger(); + SubmissionPublisher p = new SubmissionPublisher( + basicExecutor, 4); + TestSubscriber s1 = new TestSubscriber(); + s1.request = false; + TestSubscriber s2 = new TestSubscriber(); + s2.request = false; + p.subscribe(s1); + p.subscribe(s2); + s2.awaitSubscribe(); + s1.awaitSubscribe(); + for (int i = 1; i <= 4; ++i) + assertTrue(p.offer(i, (s, x) -> noopHandle(calls)) >= 0); + p.offer(4, (s, x) -> noopHandle(calls)); + assertTrue(p.offer(6, (s, x) -> noopHandle(calls)) < 0); + s1.sn.request(64); + assertTrue(p.offer(7, (s, x) -> noopHandle(calls)) < 0); + s2.sn.request(64); + p.close(); + s2.awaitComplete(); + s1.awaitComplete(); + assertTrue(calls.get() >= 4); + } + + /** + * offer succeeds if drop handler forces request + */ + public void testRecoveredHandledDroppedOffer() { + AtomicInteger calls = new AtomicInteger(); + SubmissionPublisher p = new SubmissionPublisher( + basicExecutor, 4); + TestSubscriber s1 = new TestSubscriber(); + s1.request = false; + TestSubscriber s2 = new TestSubscriber(); + s2.request = false; + p.subscribe(s1); + p.subscribe(s2); + s2.awaitSubscribe(); + s1.awaitSubscribe(); + int n = 0; + for (int i = 1; i <= 8; ++i) { + int d = p.offer(i, (s, x) -> reqHandle(calls, s)); + n = n + 2 + (d < 0 ? d : 0); + } + p.close(); + s2.awaitComplete(); + s1.awaitComplete(); + assertEquals(n, s1.nexts + s2.nexts); + assertTrue(calls.get() >= 2); + } + + /** + * Timed offer to a publisher with no subscribers returns lag 0 + */ + public void testEmptyTimedOffer() { + SubmissionPublisher p = basicPublisher(); + long startTime = System.nanoTime(); + assertEquals(0, p.offer(1, LONG_DELAY_MS, MILLISECONDS, null)); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS / 2); + } + + /** + * Timed offer with null item or TimeUnit throws NPE + */ + public void testNullTimedOffer() { + SubmissionPublisher p = basicPublisher(); + long startTime = System.nanoTime(); + try { + p.offer(null, LONG_DELAY_MS, MILLISECONDS, null); + shouldThrow(); + } catch (NullPointerException success) {} + try { + p.offer(1, LONG_DELAY_MS, null, null); + shouldThrow(); + } catch (NullPointerException success) {} + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS / 2); + } + + /** + * Timed offer returns number of lagged items if not saturated + */ + public void testLaggedTimedOffer() { + SubmissionPublisher p = basicPublisher(); + TestSubscriber s1 = new TestSubscriber(); + s1.request = false; + TestSubscriber s2 = new TestSubscriber(); + s2.request = false; + p.subscribe(s1); + p.subscribe(s2); + s2.awaitSubscribe(); + s1.awaitSubscribe(); + long startTime = System.nanoTime(); + assertTrue(p.offer(1, LONG_DELAY_MS, MILLISECONDS, null) >= 1); + assertTrue(p.offer(2, LONG_DELAY_MS, MILLISECONDS, null) >= 2); + s1.sn.request(4); + assertTrue(p.offer(3, LONG_DELAY_MS, MILLISECONDS, null) >= 3); + s2.sn.request(4); + p.offer(4, LONG_DELAY_MS, MILLISECONDS, null); + p.close(); + s2.awaitComplete(); + assertEquals(4, s2.nexts); + s1.awaitComplete(); + assertEquals(4, s2.nexts); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS / 2); + } + + /** + * Timed offer reports drops if saturated + */ + public void testDroppedTimedOffer() { + SubmissionPublisher p = new SubmissionPublisher( + basicExecutor, 4); + TestSubscriber s1 = new TestSubscriber(); + s1.request = false; + TestSubscriber s2 = new TestSubscriber(); + s2.request = false; + p.subscribe(s1); + p.subscribe(s2); + s2.awaitSubscribe(); + s1.awaitSubscribe(); + long delay = timeoutMillis(); + for (int i = 1; i <= 4; ++i) + assertTrue(p.offer(i, delay, MILLISECONDS, null) >= 0); + long startTime = System.nanoTime(); + assertTrue(p.offer(5, delay, MILLISECONDS, null) < 0); + s1.sn.request(64); + assertTrue(p.offer(6, delay, MILLISECONDS, null) < 0); + // 2 * delay should elapse but check only 1 * delay to allow timer slop + assertTrue(millisElapsedSince(startTime) >= delay); + s2.sn.request(64); + p.close(); + s2.awaitComplete(); + assertTrue(s2.nexts >= 2); + s1.awaitComplete(); + assertTrue(s1.nexts >= 2); + } + + /** + * Timed offer invokes drop handler if saturated + */ + public void testHandledDroppedTimedOffer() { + AtomicInteger calls = new AtomicInteger(); + SubmissionPublisher p = new SubmissionPublisher( + basicExecutor, 4); + TestSubscriber s1 = new TestSubscriber(); + s1.request = false; + TestSubscriber s2 = new TestSubscriber(); + s2.request = false; + p.subscribe(s1); + p.subscribe(s2); + s2.awaitSubscribe(); + s1.awaitSubscribe(); + long delay = timeoutMillis(); + for (int i = 1; i <= 4; ++i) + assertTrue(p.offer(i, delay, MILLISECONDS, (s, x) -> noopHandle(calls)) >= 0); + long startTime = System.nanoTime(); + assertTrue(p.offer(5, delay, MILLISECONDS, (s, x) -> noopHandle(calls)) < 0); + s1.sn.request(64); + assertTrue(p.offer(6, delay, MILLISECONDS, (s, x) -> noopHandle(calls)) < 0); + assertTrue(millisElapsedSince(startTime) >= delay); + s2.sn.request(64); + p.close(); + s2.awaitComplete(); + s1.awaitComplete(); + assertTrue(calls.get() >= 2); + } + + /** + * Timed offer succeeds if drop handler forces request + */ + public void testRecoveredHandledDroppedTimedOffer() { + AtomicInteger calls = new AtomicInteger(); + SubmissionPublisher p = new SubmissionPublisher( + basicExecutor, 4); + TestSubscriber s1 = new TestSubscriber(); + s1.request = false; + TestSubscriber s2 = new TestSubscriber(); + s2.request = false; + p.subscribe(s1); + p.subscribe(s2); + s2.awaitSubscribe(); + s1.awaitSubscribe(); + int n = 0; + long delay = timeoutMillis(); + long startTime = System.nanoTime(); + for (int i = 1; i <= 6; ++i) { + int d = p.offer(i, delay, MILLISECONDS, (s, x) -> reqHandle(calls, s)); + n = n + 2 + (d < 0 ? d : 0); + } + assertTrue(millisElapsedSince(startTime) >= delay); + p.close(); + s2.awaitComplete(); + s1.awaitComplete(); + assertEquals(n, s1.nexts + s2.nexts); + assertTrue(calls.get() >= 2); + } + + /** + * consume returns a CompletableFuture that is done when + * publisher completes + */ + public void testConsume() { + AtomicInteger sum = new AtomicInteger(); + SubmissionPublisher p = basicPublisher(); + CompletableFuture f = + p.consume((Integer x) -> { sum.getAndAdd(x.intValue()); }); + int n = 20; + for (int i = 1; i <= n; ++i) + p.submit(i); + p.close(); + f.join(); + assertEquals((n * (n + 1)) / 2, sum.get()); + } + + /** + * consume(null) throws NPE + */ + public void testConsumeNPE() { + SubmissionPublisher p = basicPublisher(); + try { + CompletableFuture f = p.consume(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * consume eventually stops processing published items if cancelled + */ + public void testCancelledConsume() { + AtomicInteger count = new AtomicInteger(); + SubmissionPublisher p = basicPublisher(); + CompletableFuture f = p.consume(x -> count.getAndIncrement()); + f.cancel(true); + int n = 1000000; // arbitrary limit + for (int i = 1; i <= n; ++i) + p.submit(i); + assertTrue(count.get() < n); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/SynchronousQueueTest.java b/jdk/test/java/util/concurrent/tck/SynchronousQueueTest.java new file mode 100644 index 00000000000..b4529801928 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/SynchronousQueueTest.java @@ -0,0 +1,643 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.SynchronousQueue; + +import junit.framework.Test; + +public class SynchronousQueueTest extends JSR166TestCase { + + public static class Fair extends BlockingQueueTest { + protected BlockingQueue emptyCollection() { + return new SynchronousQueue(true); + } + } + + public static class NonFair extends BlockingQueueTest { + protected BlockingQueue emptyCollection() { + return new SynchronousQueue(false); + } + } + + public static void main(String[] args) { + main(suite(), args); + } + + public static Test suite() { + return newTestSuite(SynchronousQueueTest.class, + new Fair().testSuite(), + new NonFair().testSuite()); + } + + /** + * Any SynchronousQueue is both empty and full + */ + public void testEmptyFull() { testEmptyFull(false); } + public void testEmptyFull_fair() { testEmptyFull(true); } + public void testEmptyFull(boolean fair) { + final SynchronousQueue q = new SynchronousQueue(fair); + assertTrue(q.isEmpty()); + assertEquals(0, q.size()); + assertEquals(0, q.remainingCapacity()); + assertFalse(q.offer(zero)); + } + + /** + * offer fails if no active taker + */ + public void testOffer() { testOffer(false); } + public void testOffer_fair() { testOffer(true); } + public void testOffer(boolean fair) { + SynchronousQueue q = new SynchronousQueue(fair); + assertFalse(q.offer(one)); + } + + /** + * add throws IllegalStateException if no active taker + */ + public void testAdd() { testAdd(false); } + public void testAdd_fair() { testAdd(true); } + public void testAdd(boolean fair) { + SynchronousQueue q = new SynchronousQueue(fair); + assertEquals(0, q.remainingCapacity()); + try { + q.add(one); + shouldThrow(); + } catch (IllegalStateException success) {} + } + + /** + * addAll(this) throws IllegalArgumentException + */ + public void testAddAll_self() { testAddAll_self(false); } + public void testAddAll_self_fair() { testAddAll_self(true); } + public void testAddAll_self(boolean fair) { + SynchronousQueue q = new SynchronousQueue(fair); + try { + q.addAll(q); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * addAll throws ISE if no active taker + */ + public void testAddAll_ISE() { testAddAll_ISE(false); } + public void testAddAll_ISE_fair() { testAddAll_ISE(true); } + public void testAddAll_ISE(boolean fair) { + SynchronousQueue q = new SynchronousQueue(fair); + Integer[] ints = new Integer[1]; + for (int i = 0; i < ints.length; i++) + ints[i] = i; + Collection coll = Arrays.asList(ints); + try { + q.addAll(coll); + shouldThrow(); + } catch (IllegalStateException success) {} + } + + /** + * put blocks interruptibly if no active taker + */ + public void testBlockingPut() { testBlockingPut(false); } + public void testBlockingPut_fair() { testBlockingPut(true); } + public void testBlockingPut(boolean fair) { + final SynchronousQueue q = new SynchronousQueue(fair); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + Thread.currentThread().interrupt(); + try { + q.put(99); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + q.put(99); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + assertEquals(0, q.remainingCapacity()); + } + + /** + * put blocks interruptibly waiting for take + */ + public void testPutWithTake() { testPutWithTake(false); } + public void testPutWithTake_fair() { testPutWithTake(true); } + public void testPutWithTake(boolean fair) { + final SynchronousQueue q = new SynchronousQueue(fair); + final CountDownLatch pleaseTake = new CountDownLatch(1); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + pleaseTake.countDown(); + q.put(one); + + pleaseInterrupt.countDown(); + try { + q.put(99); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseTake); + assertEquals(0, q.remainingCapacity()); + try { assertSame(one, q.take()); } + catch (InterruptedException e) { threadUnexpectedException(e); } + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + assertEquals(0, q.remainingCapacity()); + } + + /** + * timed offer times out if elements not taken + */ + public void testTimedOffer() { testTimedOffer(false); } + public void testTimedOffer_fair() { testTimedOffer(true); } + public void testTimedOffer(boolean fair) { + final SynchronousQueue q = new SynchronousQueue(fair); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + assertFalse(q.offer(new Object(), timeoutMillis(), MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + pleaseInterrupt.countDown(); + try { + q.offer(new Object(), 2 * LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * poll return null if no active putter + */ + public void testPoll() { testPoll(false); } + public void testPoll_fair() { testPoll(true); } + public void testPoll(boolean fair) { + final SynchronousQueue q = new SynchronousQueue(fair); + assertNull(q.poll()); + } + + /** + * timed poll with zero timeout times out if no active putter + */ + public void testTimedPoll0() { testTimedPoll0(false); } + public void testTimedPoll0_fair() { testTimedPoll0(true); } + public void testTimedPoll0(boolean fair) { + final SynchronousQueue q = new SynchronousQueue(fair); + try { assertNull(q.poll(0, MILLISECONDS)); } + catch (InterruptedException e) { threadUnexpectedException(e); } + } + + /** + * timed poll with nonzero timeout times out if no active putter + */ + public void testTimedPoll() { testTimedPoll(false); } + public void testTimedPoll_fair() { testTimedPoll(true); } + public void testTimedPoll(boolean fair) { + final SynchronousQueue q = new SynchronousQueue(fair); + long startTime = System.nanoTime(); + try { assertNull(q.poll(timeoutMillis(), MILLISECONDS)); } + catch (InterruptedException e) { threadUnexpectedException(e); } + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + } + + /** + * timed poll before a delayed offer times out, returning null; + * after offer succeeds; on interruption throws + */ + public void testTimedPollWithOffer() { testTimedPollWithOffer(false); } + public void testTimedPollWithOffer_fair() { testTimedPollWithOffer(true); } + public void testTimedPollWithOffer(boolean fair) { + final SynchronousQueue q = new SynchronousQueue(fair); + final CountDownLatch pleaseOffer = new CountDownLatch(1); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + assertNull(q.poll(timeoutMillis(), MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + + pleaseOffer.countDown(); + startTime = System.nanoTime(); + assertSame(zero, q.poll(LONG_DELAY_MS, MILLISECONDS)); + + Thread.currentThread().interrupt(); + try { + q.poll(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + q.poll(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + }}); + + await(pleaseOffer); + long startTime = System.nanoTime(); + try { assertTrue(q.offer(zero, LONG_DELAY_MS, MILLISECONDS)); } + catch (InterruptedException e) { threadUnexpectedException(e); } + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * peek() returns null if no active putter + */ + public void testPeek() { testPeek(false); } + public void testPeek_fair() { testPeek(true); } + public void testPeek(boolean fair) { + final SynchronousQueue q = new SynchronousQueue(fair); + assertNull(q.peek()); + } + + /** + * element() throws NoSuchElementException if no active putter + */ + public void testElement() { testElement(false); } + public void testElement_fair() { testElement(true); } + public void testElement(boolean fair) { + final SynchronousQueue q = new SynchronousQueue(fair); + try { + q.element(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * remove() throws NoSuchElementException if no active putter + */ + public void testRemove() { testRemove(false); } + public void testRemove_fair() { testRemove(true); } + public void testRemove(boolean fair) { + final SynchronousQueue q = new SynchronousQueue(fair); + try { + q.remove(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + + /** + * contains returns false + */ + public void testContains() { testContains(false); } + public void testContains_fair() { testContains(true); } + public void testContains(boolean fair) { + final SynchronousQueue q = new SynchronousQueue(fair); + assertFalse(q.contains(zero)); + } + + /** + * clear ensures isEmpty + */ + public void testClear() { testClear(false); } + public void testClear_fair() { testClear(true); } + public void testClear(boolean fair) { + final SynchronousQueue q = new SynchronousQueue(fair); + q.clear(); + assertTrue(q.isEmpty()); + } + + /** + * containsAll returns false unless empty + */ + public void testContainsAll() { testContainsAll(false); } + public void testContainsAll_fair() { testContainsAll(true); } + public void testContainsAll(boolean fair) { + final SynchronousQueue q = new SynchronousQueue(fair); + Integer[] empty = new Integer[0]; + assertTrue(q.containsAll(Arrays.asList(empty))); + Integer[] ints = new Integer[1]; ints[0] = zero; + assertFalse(q.containsAll(Arrays.asList(ints))); + } + + /** + * retainAll returns false + */ + public void testRetainAll() { testRetainAll(false); } + public void testRetainAll_fair() { testRetainAll(true); } + public void testRetainAll(boolean fair) { + final SynchronousQueue q = new SynchronousQueue(fair); + Integer[] empty = new Integer[0]; + assertFalse(q.retainAll(Arrays.asList(empty))); + Integer[] ints = new Integer[1]; ints[0] = zero; + assertFalse(q.retainAll(Arrays.asList(ints))); + } + + /** + * removeAll returns false + */ + public void testRemoveAll() { testRemoveAll(false); } + public void testRemoveAll_fair() { testRemoveAll(true); } + public void testRemoveAll(boolean fair) { + final SynchronousQueue q = new SynchronousQueue(fair); + Integer[] empty = new Integer[0]; + assertFalse(q.removeAll(Arrays.asList(empty))); + Integer[] ints = new Integer[1]; ints[0] = zero; + assertFalse(q.containsAll(Arrays.asList(ints))); + } + + /** + * toArray is empty + */ + public void testToArray() { testToArray(false); } + public void testToArray_fair() { testToArray(true); } + public void testToArray(boolean fair) { + final SynchronousQueue q = new SynchronousQueue(fair); + Object[] o = q.toArray(); + assertEquals(0, o.length); + } + + /** + * toArray(Integer array) returns its argument with the first + * element (if present) nulled out + */ + public void testToArray2() { testToArray2(false); } + public void testToArray2_fair() { testToArray2(true); } + public void testToArray2(boolean fair) { + final SynchronousQueue q + = new SynchronousQueue(fair); + Integer[] a; + + a = new Integer[0]; + assertSame(a, q.toArray(a)); + + a = new Integer[3]; + Arrays.fill(a, 42); + assertSame(a, q.toArray(a)); + assertNull(a[0]); + for (int i = 1; i < a.length; i++) + assertEquals(42, (int) a[i]); + } + + /** + * toArray(null) throws NPE + */ + public void testToArray_null() { testToArray_null(false); } + public void testToArray_null_fair() { testToArray_null(true); } + public void testToArray_null(boolean fair) { + final SynchronousQueue q = new SynchronousQueue(fair); + try { + Object[] o = q.toArray(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * iterator does not traverse any elements + */ + public void testIterator() { testIterator(false); } + public void testIterator_fair() { testIterator(true); } + public void testIterator(boolean fair) { + assertIteratorExhausted(new SynchronousQueue(fair).iterator()); + } + + /** + * iterator remove throws ISE + */ + public void testIteratorRemove() { testIteratorRemove(false); } + public void testIteratorRemove_fair() { testIteratorRemove(true); } + public void testIteratorRemove(boolean fair) { + final SynchronousQueue q = new SynchronousQueue(fair); + Iterator it = q.iterator(); + try { + it.remove(); + shouldThrow(); + } catch (IllegalStateException success) {} + } + + /** + * toString returns a non-null string + */ + public void testToString() { testToString(false); } + public void testToString_fair() { testToString(true); } + public void testToString(boolean fair) { + final SynchronousQueue q = new SynchronousQueue(fair); + String s = q.toString(); + assertNotNull(s); + } + + /** + * offer transfers elements across Executor tasks + */ + public void testOfferInExecutor() { testOfferInExecutor(false); } + public void testOfferInExecutor_fair() { testOfferInExecutor(true); } + public void testOfferInExecutor(boolean fair) { + final SynchronousQueue q = new SynchronousQueue(fair); + final CheckedBarrier threadsStarted = new CheckedBarrier(2); + final ExecutorService executor = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(executor)) { + + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(q.offer(one)); + threadsStarted.await(); + assertTrue(q.offer(one, LONG_DELAY_MS, MILLISECONDS)); + assertEquals(0, q.remainingCapacity()); + }}); + + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + assertSame(one, q.take()); + }}); + } + } + + /** + * timed poll retrieves elements across Executor threads + */ + public void testPollInExecutor() { testPollInExecutor(false); } + public void testPollInExecutor_fair() { testPollInExecutor(true); } + public void testPollInExecutor(boolean fair) { + final SynchronousQueue q = new SynchronousQueue(fair); + final CheckedBarrier threadsStarted = new CheckedBarrier(2); + final ExecutorService executor = Executors.newFixedThreadPool(2); + try (PoolCleaner cleaner = cleaner(executor)) { + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertNull(q.poll()); + threadsStarted.await(); + assertSame(one, q.poll(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(q.isEmpty()); + }}); + + executor.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.await(); + q.put(one); + }}); + } + } + + /** + * a deserialized serialized queue is usable + */ + public void testSerialization() { + final SynchronousQueue x = new SynchronousQueue(); + final SynchronousQueue y = new SynchronousQueue(false); + final SynchronousQueue z = new SynchronousQueue(true); + assertSerialEquals(x, y); + assertNotSerialEquals(x, z); + SynchronousQueue[] qs = { x, y, z }; + for (SynchronousQueue q : qs) { + SynchronousQueue clone = serialClone(q); + assertNotSame(q, clone); + assertSerialEquals(q, clone); + assertTrue(clone.isEmpty()); + assertEquals(0, clone.size()); + assertEquals(0, clone.remainingCapacity()); + assertFalse(clone.offer(zero)); + } + } + + /** + * drainTo(c) of empty queue doesn't transfer elements + */ + public void testDrainTo() { testDrainTo(false); } + public void testDrainTo_fair() { testDrainTo(true); } + public void testDrainTo(boolean fair) { + final SynchronousQueue q = new SynchronousQueue(fair); + ArrayList l = new ArrayList(); + q.drainTo(l); + assertEquals(0, q.size()); + assertEquals(0, l.size()); + } + + /** + * drainTo empties queue, unblocking a waiting put. + */ + public void testDrainToWithActivePut() { testDrainToWithActivePut(false); } + public void testDrainToWithActivePut_fair() { testDrainToWithActivePut(true); } + public void testDrainToWithActivePut(boolean fair) { + final SynchronousQueue q = new SynchronousQueue(fair); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + q.put(one); + }}); + + ArrayList l = new ArrayList(); + long startTime = System.nanoTime(); + while (l.isEmpty()) { + q.drainTo(l); + if (millisElapsedSince(startTime) > LONG_DELAY_MS) + fail("timed out"); + Thread.yield(); + } + assertTrue(l.size() == 1); + assertSame(one, l.get(0)); + awaitTermination(t); + } + + /** + * drainTo(c, n) empties up to n elements of queue into c + */ + public void testDrainToN() throws InterruptedException { + final SynchronousQueue q = new SynchronousQueue(); + Thread t1 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + q.put(one); + }}); + + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + q.put(two); + }}); + + ArrayList l = new ArrayList(); + int drained; + while ((drained = q.drainTo(l, 1)) == 0) Thread.yield(); + assertEquals(1, drained); + assertEquals(1, l.size()); + while ((drained = q.drainTo(l, 1)) == 0) Thread.yield(); + assertEquals(1, drained); + assertEquals(2, l.size()); + assertTrue(l.contains(one)); + assertTrue(l.contains(two)); + awaitTermination(t1); + awaitTermination(t2); + } + + /** + * remove(null), contains(null) always return false + */ + public void testNeverContainsNull() { + Collection q = new SynchronousQueue(); + assertFalse(q.contains(null)); + assertFalse(q.remove(null)); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/SystemTest.java b/jdk/test/java/util/concurrent/tck/SystemTest.java new file mode 100644 index 00000000000..abf3b49cdd5 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/SystemTest.java @@ -0,0 +1,96 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class SystemTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + + public static Test suite() { + return new TestSuite(SystemTest.class); + } + + /** + * Worst case rounding for millisecs; set for 60 cycle millis clock. + * This value might need to be changed on JVMs with coarser + * System.currentTimeMillis clocks. + */ + static final long MILLIS_ROUND = 17; + + /** + * Nanos between readings of millis is no longer than millis (plus + * possible rounding). + * This shows only that nano timing not (much) worse than milli. + */ + public void testNanoTime1() throws InterruptedException { + long m1 = System.currentTimeMillis(); + Thread.sleep(1); + long n1 = System.nanoTime(); + Thread.sleep(SHORT_DELAY_MS); + long n2 = System.nanoTime(); + Thread.sleep(1); + long m2 = System.currentTimeMillis(); + long millis = m2 - m1; + long nanos = n2 - n1; + assertTrue(nanos >= 0); + long nanosAsMillis = nanos / 1000000; + assertTrue(nanosAsMillis <= millis + MILLIS_ROUND); + } + + /** + * Millis between readings of nanos is less than nanos, adjusting + * for rounding. + * This shows only that nano timing not (much) worse than milli. + */ + public void testNanoTime2() throws InterruptedException { + long n1 = System.nanoTime(); + Thread.sleep(1); + long m1 = System.currentTimeMillis(); + Thread.sleep(SHORT_DELAY_MS); + long m2 = System.currentTimeMillis(); + Thread.sleep(1); + long n2 = System.nanoTime(); + long millis = m2 - m1; + long nanos = n2 - n1; + + assertTrue(nanos >= 0); + long nanosAsMillis = nanos / 1000000; + assertTrue(millis <= nanosAsMillis + MILLIS_ROUND); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/ThreadLocalRandom8Test.java b/jdk/test/java/util/concurrent/tck/ThreadLocalRandom8Test.java new file mode 100644 index 00000000000..c80af6290ec --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ThreadLocalRandom8Test.java @@ -0,0 +1,262 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.LongAdder; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ThreadLocalRandom8Test extends JSR166TestCase { + + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(ThreadLocalRandom8Test.class); + } + + // max sampled int bound + static final int MAX_INT_BOUND = (1 << 26); + + // max sampled long bound + static final long MAX_LONG_BOUND = (1L << 42); + + // Number of replications for other checks + static final int REPS = + Integer.getInteger("ThreadLocalRandom8Test.reps", 4); + + /** + * Invoking sized ints, long, doubles, with negative sizes throws + * IllegalArgumentException + */ + public void testBadStreamSize() { + ThreadLocalRandom r = ThreadLocalRandom.current(); + Runnable[] throwingActions = { + () -> r.ints(-1L), + () -> r.ints(-1L, 2, 3), + () -> r.longs(-1L), + () -> r.longs(-1L, -1L, 1L), + () -> r.doubles(-1L), + () -> r.doubles(-1L, .5, .6), + }; + assertThrows(IllegalArgumentException.class, throwingActions); + } + + /** + * Invoking bounded ints, long, doubles, with illegal bounds throws + * IllegalArgumentException + */ + public void testBadStreamBounds() { + ThreadLocalRandom r = ThreadLocalRandom.current(); + Runnable[] throwingActions = { + () -> r.ints(2, 1), + () -> r.ints(10, 42, 42), + () -> r.longs(-1L, -1L), + () -> r.longs(10, 1L, -2L), + () -> r.doubles(0.0, 0.0), + () -> r.doubles(10, .5, .4), + }; + assertThrows(IllegalArgumentException.class, throwingActions); + } + + /** + * A parallel sized stream of ints generates the given number of values + */ + public void testIntsCount() { + LongAdder counter = new LongAdder(); + ThreadLocalRandom r = ThreadLocalRandom.current(); + long size = 0; + for (int reps = 0; reps < REPS; ++reps) { + counter.reset(); + r.ints(size).parallel().forEach(x -> counter.increment()); + assertEquals(size, counter.sum()); + size += 524959; + } + } + + /** + * A parallel sized stream of longs generates the given number of values + */ + public void testLongsCount() { + LongAdder counter = new LongAdder(); + ThreadLocalRandom r = ThreadLocalRandom.current(); + long size = 0; + for (int reps = 0; reps < REPS; ++reps) { + counter.reset(); + r.longs(size).parallel().forEach(x -> counter.increment()); + assertEquals(size, counter.sum()); + size += 524959; + } + } + + /** + * A parallel sized stream of doubles generates the given number of values + */ + public void testDoublesCount() { + LongAdder counter = new LongAdder(); + ThreadLocalRandom r = ThreadLocalRandom.current(); + long size = 0; + for (int reps = 0; reps < REPS; ++reps) { + counter.reset(); + r.doubles(size).parallel().forEach(x -> counter.increment()); + assertEquals(size, counter.sum()); + size += 524959; + } + } + + /** + * Each of a parallel sized stream of bounded ints is within bounds + */ + public void testBoundedInts() { + AtomicInteger fails = new AtomicInteger(0); + ThreadLocalRandom r = ThreadLocalRandom.current(); + long size = 12345L; + for (int least = -15485867; least < MAX_INT_BOUND; least += 524959) { + for (int bound = least + 2; bound > least && bound < MAX_INT_BOUND; bound += 67867967) { + final int lo = least, hi = bound; + r.ints(size, lo, hi).parallel().forEach( + x -> { + if (x < lo || x >= hi) + fails.getAndIncrement(); }); + } + } + assertEquals(0, fails.get()); + } + + /** + * Each of a parallel sized stream of bounded longs is within bounds + */ + public void testBoundedLongs() { + AtomicInteger fails = new AtomicInteger(0); + ThreadLocalRandom r = ThreadLocalRandom.current(); + long size = 123L; + for (long least = -86028121; least < MAX_LONG_BOUND; least += 1982451653L) { + for (long bound = least + 2; bound > least && bound < MAX_LONG_BOUND; bound += Math.abs(bound * 7919)) { + final long lo = least, hi = bound; + r.longs(size, lo, hi).parallel().forEach( + x -> { + if (x < lo || x >= hi) + fails.getAndIncrement(); }); + } + } + assertEquals(0, fails.get()); + } + + /** + * Each of a parallel sized stream of bounded doubles is within bounds + */ + public void testBoundedDoubles() { + AtomicInteger fails = new AtomicInteger(0); + ThreadLocalRandom r = ThreadLocalRandom.current(); + long size = 456; + for (double least = 0.00011; least < 1.0e20; least *= 9) { + for (double bound = least * 1.0011; bound < 1.0e20; bound *= 17) { + final double lo = least, hi = bound; + r.doubles(size, lo, hi).parallel().forEach( + x -> { + if (x < lo || x >= hi) + fails.getAndIncrement(); }); + } + } + assertEquals(0, fails.get()); + } + + /** + * A parallel unsized stream of ints generates at least 100 values + */ + public void testUnsizedIntsCount() { + LongAdder counter = new LongAdder(); + ThreadLocalRandom r = ThreadLocalRandom.current(); + long size = 100; + r.ints().limit(size).parallel().forEach(x -> counter.increment()); + assertEquals(size, counter.sum()); + } + + /** + * A parallel unsized stream of longs generates at least 100 values + */ + public void testUnsizedLongsCount() { + LongAdder counter = new LongAdder(); + ThreadLocalRandom r = ThreadLocalRandom.current(); + long size = 100; + r.longs().limit(size).parallel().forEach(x -> counter.increment()); + assertEquals(size, counter.sum()); + } + + /** + * A parallel unsized stream of doubles generates at least 100 values + */ + public void testUnsizedDoublesCount() { + LongAdder counter = new LongAdder(); + ThreadLocalRandom r = ThreadLocalRandom.current(); + long size = 100; + r.doubles().limit(size).parallel().forEach(x -> counter.increment()); + assertEquals(size, counter.sum()); + } + + /** + * A sequential unsized stream of ints generates at least 100 values + */ + public void testUnsizedIntsCountSeq() { + LongAdder counter = new LongAdder(); + ThreadLocalRandom r = ThreadLocalRandom.current(); + long size = 100; + r.ints().limit(size).forEach(x -> counter.increment()); + assertEquals(size, counter.sum()); + } + + /** + * A sequential unsized stream of longs generates at least 100 values + */ + public void testUnsizedLongsCountSeq() { + LongAdder counter = new LongAdder(); + ThreadLocalRandom r = ThreadLocalRandom.current(); + long size = 100; + r.longs().limit(size).forEach(x -> counter.increment()); + assertEquals(size, counter.sum()); + } + + /** + * A sequential unsized stream of doubles generates at least 100 values + */ + public void testUnsizedDoublesCountSeq() { + LongAdder counter = new LongAdder(); + ThreadLocalRandom r = ThreadLocalRandom.current(); + long size = 100; + r.doubles().limit(size).forEach(x -> counter.increment()); + assertEquals(size, counter.sum()); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/ThreadLocalRandomTest.java b/jdk/test/java/util/concurrent/tck/ThreadLocalRandomTest.java new file mode 100644 index 00000000000..da50bbe2495 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ThreadLocalRandomTest.java @@ -0,0 +1,368 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ThreadLocalRandomTest extends JSR166TestCase { + + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(ThreadLocalRandomTest.class); + } + + /* + * Testing coverage notes: + * + * We don't test randomness properties, but only that repeated + * calls, up to NCALLS tries, produce at least one different + * result. For bounded versions, we sample various intervals + * across multiples of primes. + */ + + // max numbers of calls to detect getting stuck on one value + static final int NCALLS = 10000; + + // max sampled int bound + static final int MAX_INT_BOUND = (1 << 28); + + // max sampled long bound + static final long MAX_LONG_BOUND = (1L << 42); + + // Number of replications for other checks + static final int REPS = 20; + + /** + * setSeed throws UnsupportedOperationException + */ + public void testSetSeed() { + try { + ThreadLocalRandom.current().setSeed(17); + shouldThrow(); + } catch (UnsupportedOperationException success) {} + } + + /** + * Repeated calls to nextInt produce at least two distinct results + */ + public void testNextInt() { + int f = ThreadLocalRandom.current().nextInt(); + int i = 0; + while (i < NCALLS && ThreadLocalRandom.current().nextInt() == f) + ++i; + assertTrue(i < NCALLS); + } + + /** + * Repeated calls to nextLong produce at least two distinct results + */ + public void testNextLong() { + long f = ThreadLocalRandom.current().nextLong(); + int i = 0; + while (i < NCALLS && ThreadLocalRandom.current().nextLong() == f) + ++i; + assertTrue(i < NCALLS); + } + + /** + * Repeated calls to nextBoolean produce at least two distinct results + */ + public void testNextBoolean() { + boolean f = ThreadLocalRandom.current().nextBoolean(); + int i = 0; + while (i < NCALLS && ThreadLocalRandom.current().nextBoolean() == f) + ++i; + assertTrue(i < NCALLS); + } + + /** + * Repeated calls to nextFloat produce at least two distinct results + */ + public void testNextFloat() { + float f = ThreadLocalRandom.current().nextFloat(); + int i = 0; + while (i < NCALLS && ThreadLocalRandom.current().nextFloat() == f) + ++i; + assertTrue(i < NCALLS); + } + + /** + * Repeated calls to nextDouble produce at least two distinct results + */ + public void testNextDouble() { + double f = ThreadLocalRandom.current().nextDouble(); + int i = 0; + while (i < NCALLS && ThreadLocalRandom.current().nextDouble() == f) + ++i; + assertTrue(i < NCALLS); + } + + /** + * Repeated calls to nextGaussian produce at least two distinct results + */ + public void testNextGaussian() { + double f = ThreadLocalRandom.current().nextGaussian(); + int i = 0; + while (i < NCALLS && ThreadLocalRandom.current().nextGaussian() == f) + ++i; + assertTrue(i < NCALLS); + } + + /** + * nextInt(non-positive) throws IllegalArgumentException + */ + public void testNextIntBoundNonPositive() { + ThreadLocalRandom rnd = ThreadLocalRandom.current(); + for (int bound : new int[] { 0, -17, Integer.MIN_VALUE }) { + try { + rnd.nextInt(bound); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * nextInt(least >= bound) throws IllegalArgumentException + */ + public void testNextIntBadBounds() { + int[][] badBoundss = { + { 17, 2 }, + { -42, -42 }, + { Integer.MAX_VALUE, Integer.MIN_VALUE }, + }; + ThreadLocalRandom rnd = ThreadLocalRandom.current(); + for (int[] badBounds : badBoundss) { + try { + rnd.nextInt(badBounds[0], badBounds[1]); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * nextInt(bound) returns 0 <= value < bound; + * repeated calls produce at least two distinct results + */ + public void testNextIntBounded() { + // sample bound space across prime number increments + for (int bound = 2; bound < MAX_INT_BOUND; bound += 524959) { + int f = ThreadLocalRandom.current().nextInt(bound); + assertTrue(0 <= f && f < bound); + int i = 0; + int j; + while (i < NCALLS && + (j = ThreadLocalRandom.current().nextInt(bound)) == f) { + assertTrue(0 <= j && j < bound); + ++i; + } + assertTrue(i < NCALLS); + } + } + + /** + * nextInt(least, bound) returns least <= value < bound; + * repeated calls produce at least two distinct results + */ + public void testNextIntBounded2() { + for (int least = -15485863; least < MAX_INT_BOUND; least += 524959) { + for (int bound = least + 2; bound > least && bound < MAX_INT_BOUND; bound += 49979687) { + int f = ThreadLocalRandom.current().nextInt(least, bound); + assertTrue(least <= f && f < bound); + int i = 0; + int j; + while (i < NCALLS && + (j = ThreadLocalRandom.current().nextInt(least, bound)) == f) { + assertTrue(least <= j && j < bound); + ++i; + } + assertTrue(i < NCALLS); + } + } + } + + /** + * nextLong(non-positive) throws IllegalArgumentException + */ + public void testNextLongBoundNonPositive() { + ThreadLocalRandom rnd = ThreadLocalRandom.current(); + for (long bound : new long[] { 0L, -17L, Long.MIN_VALUE }) { + try { + rnd.nextLong(bound); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * nextLong(least >= bound) throws IllegalArgumentException + */ + public void testNextLongBadBounds() { + long[][] badBoundss = { + { 17L, 2L }, + { -42L, -42L }, + { Long.MAX_VALUE, Long.MIN_VALUE }, + }; + ThreadLocalRandom rnd = ThreadLocalRandom.current(); + for (long[] badBounds : badBoundss) { + try { + rnd.nextLong(badBounds[0], badBounds[1]); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * nextLong(bound) returns 0 <= value < bound; + * repeated calls produce at least two distinct results + */ + public void testNextLongBounded() { + for (long bound = 2; bound < MAX_LONG_BOUND; bound += 15485863) { + long f = ThreadLocalRandom.current().nextLong(bound); + assertTrue(0 <= f && f < bound); + int i = 0; + long j; + while (i < NCALLS && + (j = ThreadLocalRandom.current().nextLong(bound)) == f) { + assertTrue(0 <= j && j < bound); + ++i; + } + assertTrue(i < NCALLS); + } + } + + /** + * nextLong(least, bound) returns least <= value < bound; + * repeated calls produce at least two distinct results + */ + public void testNextLongBounded2() { + for (long least = -86028121; least < MAX_LONG_BOUND; least += 982451653L) { + for (long bound = least + 2; bound > least && bound < MAX_LONG_BOUND; bound += Math.abs(bound * 7919)) { + long f = ThreadLocalRandom.current().nextLong(least, bound); + assertTrue(least <= f && f < bound); + int i = 0; + long j; + while (i < NCALLS && + (j = ThreadLocalRandom.current().nextLong(least, bound)) == f) { + assertTrue(least <= j && j < bound); + ++i; + } + assertTrue(i < NCALLS); + } + } + } + + /** + * nextDouble(non-positive) throws IllegalArgumentException + */ + public void testNextDoubleBoundNonPositive() { + ThreadLocalRandom rnd = ThreadLocalRandom.current(); + double[] badBounds = { + 0.0d, + -17.0d, + -Double.MIN_VALUE, + Double.NEGATIVE_INFINITY, + Double.NaN, + }; + for (double bound : badBounds) { + try { + rnd.nextDouble(bound); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * nextDouble(least, bound) returns least <= value < bound; + * repeated calls produce at least two distinct results + */ + public void testNextDoubleBounded2() { + for (double least = 0.0001; least < 1.0e20; least *= 8) { + for (double bound = least * 1.001; bound < 1.0e20; bound *= 16) { + double f = ThreadLocalRandom.current().nextDouble(least, bound); + assertTrue(least <= f && f < bound); + int i = 0; + double j; + while (i < NCALLS && + (j = ThreadLocalRandom.current().nextDouble(least, bound)) == f) { + assertTrue(least <= j && j < bound); + ++i; + } + assertTrue(i < NCALLS); + } + } + } + + /** + * Different threads produce different pseudo-random sequences + */ + public void testDifferentSequences() { + // Don't use main thread's ThreadLocalRandom - it is likely to + // be polluted by previous tests. + final AtomicReference threadLocalRandom = + new AtomicReference(); + final AtomicLong rand = new AtomicLong(); + + long firstRand = 0; + ThreadLocalRandom firstThreadLocalRandom = null; + + Runnable getRandomState = new CheckedRunnable() { + public void realRun() { + ThreadLocalRandom current = ThreadLocalRandom.current(); + assertSame(current, ThreadLocalRandom.current()); + // test bug: the following is not guaranteed and not true in JDK8 + // assertNotSame(current, threadLocalRandom.get()); + rand.set(current.nextLong()); + threadLocalRandom.set(current); + }}; + + Thread first = newStartedThread(getRandomState); + awaitTermination(first); + firstRand = rand.get(); + firstThreadLocalRandom = threadLocalRandom.get(); + + for (int i = 0; i < NCALLS; i++) { + Thread t = newStartedThread(getRandomState); + awaitTermination(t); + if (firstRand != rand.get()) + return; + } + fail("all threads generate the same pseudo-random sequence"); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/ThreadLocalTest.java b/jdk/test/java/util/concurrent/tck/ThreadLocalTest.java new file mode 100644 index 00000000000..51d2197f4d7 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ThreadLocalTest.java @@ -0,0 +1,128 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ThreadLocalTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + + public static Test suite() { + return new TestSuite(ThreadLocalTest.class); + } + + static ThreadLocal tl = new ThreadLocal() { + public Integer initialValue() { + return one; + } + }; + + static InheritableThreadLocal itl = + new InheritableThreadLocal() { + protected Integer initialValue() { + return zero; + } + + protected Integer childValue(Integer parentValue) { + return new Integer(parentValue.intValue() + 1); + } + }; + + /** + * remove causes next access to return initial value + */ + public void testRemove() { + assertSame(tl.get(), one); + tl.set(two); + assertSame(tl.get(), two); + tl.remove(); + assertSame(tl.get(), one); + } + + /** + * remove in InheritableThreadLocal causes next access to return + * initial value + */ + public void testRemoveITL() { + assertSame(itl.get(), zero); + itl.set(two); + assertSame(itl.get(), two); + itl.remove(); + assertSame(itl.get(), zero); + } + + private class ITLThread extends Thread { + final int[] x; + ITLThread(int[] array) { x = array; } + public void run() { + Thread child = null; + if (itl.get().intValue() < x.length - 1) { + child = new ITLThread(x); + child.start(); + } + Thread.yield(); + + int threadId = itl.get().intValue(); + for (int j = 0; j < threadId; j++) { + x[threadId]++; + Thread.yield(); + } + + if (child != null) { // Wait for child (if any) + try { + child.join(); + } catch (InterruptedException e) { + threadUnexpectedException(e); + } + } + } + } + + /** + * InheritableThreadLocal propagates generic values. + */ + public void testGenericITL() throws InterruptedException { + final int threadCount = 10; + final int[] x = new int[threadCount]; + Thread progenitor = new ITLThread(x); + progenitor.start(); + progenitor.join(); + for (int i = 0; i < threadCount; i++) { + assertEquals(i, x[i]); + } + } +} diff --git a/jdk/test/java/util/concurrent/tck/ThreadPoolExecutorSubclassTest.java b/jdk/test/java/util/concurrent/tck/ThreadPoolExecutorSubclassTest.java new file mode 100644 index 00000000000..3959527af70 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ThreadPoolExecutorSubclassTest.java @@ -0,0 +1,2062 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.RunnableFuture; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ThreadPoolExecutorSubclassTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(ThreadPoolExecutorSubclassTest.class); + } + + static class CustomTask implements RunnableFuture { + final Callable callable; + final ReentrantLock lock = new ReentrantLock(); + final Condition cond = lock.newCondition(); + boolean done; + boolean cancelled; + V result; + Thread thread; + Exception exception; + CustomTask(Callable c) { + if (c == null) throw new NullPointerException(); + callable = c; + } + CustomTask(final Runnable r, final V res) { + if (r == null) throw new NullPointerException(); + callable = new Callable() { + public V call() throws Exception { r.run(); return res; }}; + } + public boolean isDone() { + lock.lock(); try { return done; } finally { lock.unlock() ; } + } + public boolean isCancelled() { + lock.lock(); try { return cancelled; } finally { lock.unlock() ; } + } + public boolean cancel(boolean mayInterrupt) { + lock.lock(); + try { + if (!done) { + cancelled = true; + done = true; + if (mayInterrupt && thread != null) + thread.interrupt(); + return true; + } + return false; + } + finally { lock.unlock() ; } + } + public void run() { + lock.lock(); + try { + if (done) + return; + thread = Thread.currentThread(); + } + finally { lock.unlock() ; } + V v = null; + Exception e = null; + try { + v = callable.call(); + } + catch (Exception ex) { + e = ex; + } + lock.lock(); + try { + if (!done) { + result = v; + exception = e; + done = true; + thread = null; + cond.signalAll(); + } + } + finally { lock.unlock(); } + } + public V get() throws InterruptedException, ExecutionException { + lock.lock(); + try { + while (!done) + cond.await(); + if (cancelled) + throw new CancellationException(); + if (exception != null) + throw new ExecutionException(exception); + return result; + } + finally { lock.unlock(); } + } + public V get(long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + long nanos = unit.toNanos(timeout); + lock.lock(); + try { + while (!done) { + if (nanos <= 0L) + throw new TimeoutException(); + nanos = cond.awaitNanos(nanos); + } + if (cancelled) + throw new CancellationException(); + if (exception != null) + throw new ExecutionException(exception); + return result; + } + finally { lock.unlock(); } + } + } + + static class CustomTPE extends ThreadPoolExecutor { + protected RunnableFuture newTaskFor(Callable c) { + return new CustomTask(c); + } + protected RunnableFuture newTaskFor(Runnable r, V v) { + return new CustomTask(r, v); + } + + CustomTPE(int corePoolSize, + int maximumPoolSize, + long keepAliveTime, + TimeUnit unit, + BlockingQueue workQueue) { + super(corePoolSize, maximumPoolSize, keepAliveTime, unit, + workQueue); + } + CustomTPE(int corePoolSize, + int maximumPoolSize, + long keepAliveTime, + TimeUnit unit, + BlockingQueue workQueue, + ThreadFactory threadFactory) { + super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, + threadFactory); + } + + CustomTPE(int corePoolSize, + int maximumPoolSize, + long keepAliveTime, + TimeUnit unit, + BlockingQueue workQueue, + RejectedExecutionHandler handler) { + super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, + handler); + } + CustomTPE(int corePoolSize, + int maximumPoolSize, + long keepAliveTime, + TimeUnit unit, + BlockingQueue workQueue, + ThreadFactory threadFactory, + RejectedExecutionHandler handler) { + super(corePoolSize, maximumPoolSize, keepAliveTime, unit, + workQueue, threadFactory, handler); + } + + final CountDownLatch beforeCalled = new CountDownLatch(1); + final CountDownLatch afterCalled = new CountDownLatch(1); + final CountDownLatch terminatedCalled = new CountDownLatch(1); + + public CustomTPE() { + super(1, 1, LONG_DELAY_MS, MILLISECONDS, new SynchronousQueue()); + } + protected void beforeExecute(Thread t, Runnable r) { + beforeCalled.countDown(); + } + protected void afterExecute(Runnable r, Throwable t) { + afterCalled.countDown(); + } + protected void terminated() { + terminatedCalled.countDown(); + } + + public boolean beforeCalled() { + return beforeCalled.getCount() == 0; + } + public boolean afterCalled() { + return afterCalled.getCount() == 0; + } + public boolean terminatedCalled() { + return terminatedCalled.getCount() == 0; + } + } + + static class FailingThreadFactory implements ThreadFactory { + int calls = 0; + public Thread newThread(Runnable r) { + if (++calls > 1) return null; + return new Thread(r); + } + } + + /** + * execute successfully executes a runnable + */ + public void testExecute() throws InterruptedException { + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + 2 * LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch done = new CountDownLatch(1); + final Runnable task = new CheckedRunnable() { + public void realRun() { done.countDown(); }}; + p.execute(task); + assertTrue(done.await(LONG_DELAY_MS, MILLISECONDS)); + } + } + + /** + * getActiveCount increases but doesn't overestimate, when a + * thread becomes active + */ + public void testGetActiveCount() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + assertEquals(0, p.getActiveCount()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + assertEquals(1, p.getActiveCount()); + await(done); + }}); + await(threadStarted); + assertEquals(1, p.getActiveCount()); + } + } + + /** + * prestartCoreThread starts a thread if under corePoolSize, else doesn't + */ + public void testPrestartCoreThread() { + final ThreadPoolExecutor p = + new CustomTPE(2, 6, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(0, p.getPoolSize()); + assertTrue(p.prestartCoreThread()); + assertEquals(1, p.getPoolSize()); + assertTrue(p.prestartCoreThread()); + assertEquals(2, p.getPoolSize()); + assertFalse(p.prestartCoreThread()); + assertEquals(2, p.getPoolSize()); + p.setCorePoolSize(4); + assertTrue(p.prestartCoreThread()); + assertEquals(3, p.getPoolSize()); + assertTrue(p.prestartCoreThread()); + assertEquals(4, p.getPoolSize()); + assertFalse(p.prestartCoreThread()); + assertEquals(4, p.getPoolSize()); + } + } + + /** + * prestartAllCoreThreads starts all corePoolSize threads + */ + public void testPrestartAllCoreThreads() { + final ThreadPoolExecutor p = + new CustomTPE(2, 6, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(0, p.getPoolSize()); + p.prestartAllCoreThreads(); + assertEquals(2, p.getPoolSize()); + p.prestartAllCoreThreads(); + assertEquals(2, p.getPoolSize()); + p.setCorePoolSize(4); + p.prestartAllCoreThreads(); + assertEquals(4, p.getPoolSize()); + p.prestartAllCoreThreads(); + assertEquals(4, p.getPoolSize()); + } + } + + /** + * getCompletedTaskCount increases, but doesn't overestimate, + * when tasks complete + */ + public void testGetCompletedTaskCount() throws InterruptedException { + final ThreadPoolExecutor p = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + final CountDownLatch threadProceed = new CountDownLatch(1); + final CountDownLatch threadDone = new CountDownLatch(1); + assertEquals(0, p.getCompletedTaskCount()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + assertEquals(0, p.getCompletedTaskCount()); + threadProceed.await(); + threadDone.countDown(); + }}); + await(threadStarted); + assertEquals(0, p.getCompletedTaskCount()); + threadProceed.countDown(); + threadDone.await(); + long startTime = System.nanoTime(); + while (p.getCompletedTaskCount() != 1) { + if (millisElapsedSince(startTime) > LONG_DELAY_MS) + fail("timed out"); + Thread.yield(); + } + } + } + + /** + * getCorePoolSize returns size given in constructor if not otherwise set + */ + public void testGetCorePoolSize() { + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(1, p.getCorePoolSize()); + } + } + + /** + * getKeepAliveTime returns value given in constructor if not otherwise set + */ + public void testGetKeepAliveTime() { + final ThreadPoolExecutor p = + new CustomTPE(2, 2, + 1000, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(1, p.getKeepAliveTime(SECONDS)); + } + } + + /** + * getThreadFactory returns factory in constructor if not set + */ + public void testGetThreadFactory() { + final ThreadFactory threadFactory = new SimpleThreadFactory(); + final ThreadPoolExecutor p = + new CustomTPE(1, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10), + threadFactory, + new NoOpREHandler()); + try (PoolCleaner cleaner = cleaner(p)) { + assertSame(threadFactory, p.getThreadFactory()); + } + } + + /** + * setThreadFactory sets the thread factory returned by getThreadFactory + */ + public void testSetThreadFactory() { + final ThreadPoolExecutor p = + new CustomTPE(1, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + ThreadFactory threadFactory = new SimpleThreadFactory(); + p.setThreadFactory(threadFactory); + assertSame(threadFactory, p.getThreadFactory()); + } + } + + /** + * setThreadFactory(null) throws NPE + */ + public void testSetThreadFactoryNull() { + final ThreadPoolExecutor p = + new CustomTPE(1, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setThreadFactory(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * getRejectedExecutionHandler returns handler in constructor if not set + */ + public void testGetRejectedExecutionHandler() { + final RejectedExecutionHandler handler = new NoOpREHandler(); + final ThreadPoolExecutor p = + new CustomTPE(1, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10), + handler); + try (PoolCleaner cleaner = cleaner(p)) { + assertSame(handler, p.getRejectedExecutionHandler()); + } + } + + /** + * setRejectedExecutionHandler sets the handler returned by + * getRejectedExecutionHandler + */ + public void testSetRejectedExecutionHandler() { + final ThreadPoolExecutor p = + new CustomTPE(1, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + RejectedExecutionHandler handler = new NoOpREHandler(); + p.setRejectedExecutionHandler(handler); + assertSame(handler, p.getRejectedExecutionHandler()); + } + } + + /** + * setRejectedExecutionHandler(null) throws NPE + */ + public void testSetRejectedExecutionHandlerNull() { + final ThreadPoolExecutor p = + new CustomTPE(1, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setRejectedExecutionHandler(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * getLargestPoolSize increases, but doesn't overestimate, when + * multiple threads active + */ + public void testGetLargestPoolSize() throws InterruptedException { + final int THREADS = 3; + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = + new CustomTPE(THREADS, THREADS, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p, done)) { + assertEquals(0, p.getLargestPoolSize()); + final CountDownLatch threadsStarted = new CountDownLatch(THREADS); + for (int i = 0; i < THREADS; i++) + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.countDown(); + await(done); + assertEquals(THREADS, p.getLargestPoolSize()); + }}); + await(threadsStarted); + assertEquals(THREADS, p.getLargestPoolSize()); + } + assertEquals(THREADS, p.getLargestPoolSize()); + } + + /** + * getMaximumPoolSize returns value given in constructor if not + * otherwise set + */ + public void testGetMaximumPoolSize() { + final ThreadPoolExecutor p = + new CustomTPE(2, 3, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(3, p.getMaximumPoolSize()); + p.setMaximumPoolSize(5); + assertEquals(5, p.getMaximumPoolSize()); + p.setMaximumPoolSize(4); + assertEquals(4, p.getMaximumPoolSize()); + } + } + + /** + * getPoolSize increases, but doesn't overestimate, when threads + * become active + */ + public void testGetPoolSize() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p, done)) { + assertEquals(0, p.getPoolSize()); + final CountDownLatch threadStarted = new CountDownLatch(1); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + assertEquals(1, p.getPoolSize()); + await(done); + }}); + await(threadStarted); + assertEquals(1, p.getPoolSize()); + } + } + + /** + * getTaskCount increases, but doesn't overestimate, when tasks submitted + */ + public void testGetTaskCount() throws InterruptedException { + final int TASKS = 3; + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + assertEquals(0, p.getTaskCount()); + assertEquals(0, p.getCompletedTaskCount()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + await(done); + }}); + await(threadStarted); + assertEquals(1, p.getTaskCount()); + assertEquals(0, p.getCompletedTaskCount()); + for (int i = 0; i < TASKS; i++) { + assertEquals(1 + i, p.getTaskCount()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + assertEquals(1 + TASKS, p.getTaskCount()); + await(done); + }}); + } + assertEquals(1 + TASKS, p.getTaskCount()); + assertEquals(0, p.getCompletedTaskCount()); + } + assertEquals(1 + TASKS, p.getTaskCount()); + assertEquals(1 + TASKS, p.getCompletedTaskCount()); + } + + /** + * isShutdown is false before shutdown, true after + */ + public void testIsShutdown() { + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + assertFalse(p.isShutdown()); + try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.isShutdown()); + } + } + + /** + * isTerminated is false before termination, true after + */ + public void testIsTerminated() throws InterruptedException { + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + final CountDownLatch done = new CountDownLatch(1); + assertFalse(p.isTerminating()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(p.isTerminating()); + threadStarted.countDown(); + await(done); + }}); + await(threadStarted); + assertFalse(p.isTerminating()); + done.countDown(); + try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + assertFalse(p.isTerminating()); + } + } + + /** + * isTerminating is not true when running or when terminated + */ + public void testIsTerminating() throws InterruptedException { + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + final CountDownLatch done = new CountDownLatch(1); + assertFalse(p.isTerminating()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(p.isTerminating()); + threadStarted.countDown(); + await(done); + }}); + await(threadStarted); + assertFalse(p.isTerminating()); + done.countDown(); + try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + assertFalse(p.isTerminating()); + } + } + + /** + * getQueue returns the work queue, which contains queued tasks + */ + public void testGetQueue() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); + final BlockingQueue q = new ArrayBlockingQueue(10); + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + q); + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + FutureTask[] tasks = new FutureTask[5]; + for (int i = 0; i < tasks.length; i++) { + Callable task = new CheckedCallable() { + public Boolean realCall() throws InterruptedException { + threadStarted.countDown(); + assertSame(q, p.getQueue()); + await(done); + return Boolean.TRUE; + }}; + tasks[i] = new FutureTask(task); + p.execute(tasks[i]); + } + await(threadStarted); + assertSame(q, p.getQueue()); + assertFalse(q.contains(tasks[0])); + assertTrue(q.contains(tasks[tasks.length - 1])); + assertEquals(tasks.length - 1, q.size()); + } + } + + /** + * remove(task) removes queued task, and fails to remove active task + */ + public void testRemove() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); + BlockingQueue q = new ArrayBlockingQueue(10); + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + q); + try (PoolCleaner cleaner = cleaner(p, done)) { + Runnable[] tasks = new Runnable[6]; + final CountDownLatch threadStarted = new CountDownLatch(1); + for (int i = 0; i < tasks.length; i++) { + tasks[i] = new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + await(done); + }}; + p.execute(tasks[i]); + } + await(threadStarted); + assertFalse(p.remove(tasks[0])); + assertTrue(q.contains(tasks[4])); + assertTrue(q.contains(tasks[3])); + assertTrue(p.remove(tasks[4])); + assertFalse(p.remove(tasks[4])); + assertFalse(q.contains(tasks[4])); + assertTrue(q.contains(tasks[3])); + assertTrue(p.remove(tasks[3])); + assertFalse(q.contains(tasks[3])); + } + } + + /** + * purge removes cancelled tasks from the queue + */ + public void testPurge() throws InterruptedException { + final CountDownLatch threadStarted = new CountDownLatch(1); + final CountDownLatch done = new CountDownLatch(1); + final BlockingQueue q = new ArrayBlockingQueue(10); + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + q); + try (PoolCleaner cleaner = cleaner(p, done)) { + FutureTask[] tasks = new FutureTask[5]; + for (int i = 0; i < tasks.length; i++) { + Callable task = new CheckedCallable() { + public Boolean realCall() throws InterruptedException { + threadStarted.countDown(); + await(done); + return Boolean.TRUE; + }}; + tasks[i] = new FutureTask(task); + p.execute(tasks[i]); + } + await(threadStarted); + assertEquals(tasks.length, p.getTaskCount()); + assertEquals(tasks.length - 1, q.size()); + assertEquals(1L, p.getActiveCount()); + assertEquals(0L, p.getCompletedTaskCount()); + tasks[4].cancel(true); + tasks[3].cancel(false); + p.purge(); + assertEquals(tasks.length - 3, q.size()); + assertEquals(tasks.length - 2, p.getTaskCount()); + p.purge(); // Nothing to do + assertEquals(tasks.length - 3, q.size()); + assertEquals(tasks.length - 2, p.getTaskCount()); + } + } + + /** + * shutdownNow returns a list containing tasks that were not run, + * and those tasks are drained from the queue + */ + public void testShutdownNow() throws InterruptedException { + final int poolSize = 2; + final int count = 5; + final AtomicInteger ran = new AtomicInteger(0); + final ThreadPoolExecutor p = + new CustomTPE(poolSize, poolSize, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + final CountDownLatch threadsStarted = new CountDownLatch(poolSize); + Runnable waiter = new CheckedRunnable() { public void realRun() { + threadsStarted.countDown(); + try { + MILLISECONDS.sleep(2 * LONG_DELAY_MS); + } catch (InterruptedException success) {} + ran.getAndIncrement(); + }}; + for (int i = 0; i < count; i++) + p.execute(waiter); + await(threadsStarted); + assertEquals(poolSize, p.getActiveCount()); + assertEquals(0, p.getCompletedTaskCount()); + final List queuedTasks; + try { + queuedTasks = p.shutdownNow(); + } catch (SecurityException ok) { + return; // Allowed in case test doesn't have privs + } + assertTrue(p.isShutdown()); + assertTrue(p.getQueue().isEmpty()); + assertEquals(count - poolSize, queuedTasks.size()); + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + assertEquals(poolSize, ran.get()); + assertEquals(poolSize, p.getCompletedTaskCount()); + } + + // Exception Tests + + /** + * Constructor throws if corePoolSize argument is less than zero + */ + public void testConstructor1() { + try { + new CustomTPE(-1, 1, 1L, SECONDS, + new ArrayBlockingQueue(10)); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if maximumPoolSize is less than zero + */ + public void testConstructor2() { + try { + new CustomTPE(1, -1, 1L, SECONDS, + new ArrayBlockingQueue(10)); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if maximumPoolSize is equal to zero + */ + public void testConstructor3() { + try { + new CustomTPE(1, 0, 1L, SECONDS, + new ArrayBlockingQueue(10)); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if keepAliveTime is less than zero + */ + public void testConstructor4() { + try { + new CustomTPE(1, 2, -1L, SECONDS, + new ArrayBlockingQueue(10)); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if corePoolSize is greater than the maximumPoolSize + */ + public void testConstructor5() { + try { + new CustomTPE(2, 1, 1L, SECONDS, + new ArrayBlockingQueue(10)); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if workQueue is set to null + */ + public void testConstructorNullPointerException() { + try { + new CustomTPE(1, 2, 1L, SECONDS, null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Constructor throws if corePoolSize argument is less than zero + */ + public void testConstructor6() { + try { + new CustomTPE(-1, 1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if maximumPoolSize is less than zero + */ + public void testConstructor7() { + try { + new CustomTPE(1,-1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if maximumPoolSize is equal to zero + */ + public void testConstructor8() { + try { + new CustomTPE(1, 0, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if keepAliveTime is less than zero + */ + public void testConstructor9() { + try { + new CustomTPE(1, 2, -1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if corePoolSize is greater than the maximumPoolSize + */ + public void testConstructor10() { + try { + new CustomTPE(2, 1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if workQueue is set to null + */ + public void testConstructorNullPointerException2() { + try { + new CustomTPE(1, 2, 1L, SECONDS, null, new SimpleThreadFactory()); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Constructor throws if threadFactory is set to null + */ + public void testConstructorNullPointerException3() { + try { + new CustomTPE(1, 2, 1L, SECONDS, + new ArrayBlockingQueue(10), + (ThreadFactory) null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Constructor throws if corePoolSize argument is less than zero + */ + public void testConstructor11() { + try { + new CustomTPE(-1, 1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new NoOpREHandler()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if maximumPoolSize is less than zero + */ + public void testConstructor12() { + try { + new CustomTPE(1, -1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new NoOpREHandler()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if maximumPoolSize is equal to zero + */ + public void testConstructor13() { + try { + new CustomTPE(1, 0, 1L, SECONDS, + new ArrayBlockingQueue(10), + new NoOpREHandler()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if keepAliveTime is less than zero + */ + public void testConstructor14() { + try { + new CustomTPE(1, 2, -1L, SECONDS, + new ArrayBlockingQueue(10), + new NoOpREHandler()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if corePoolSize is greater than the maximumPoolSize + */ + public void testConstructor15() { + try { + new CustomTPE(2, 1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new NoOpREHandler()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if workQueue is set to null + */ + public void testConstructorNullPointerException4() { + try { + new CustomTPE(1, 2, 1L, SECONDS, + null, + new NoOpREHandler()); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Constructor throws if handler is set to null + */ + public void testConstructorNullPointerException5() { + try { + new CustomTPE(1, 2, 1L, SECONDS, + new ArrayBlockingQueue(10), + (RejectedExecutionHandler) null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Constructor throws if corePoolSize argument is less than zero + */ + public void testConstructor16() { + try { + new CustomTPE(-1, 1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory(), + new NoOpREHandler()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if maximumPoolSize is less than zero + */ + public void testConstructor17() { + try { + new CustomTPE(1, -1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory(), + new NoOpREHandler()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if maximumPoolSize is equal to zero + */ + public void testConstructor18() { + try { + new CustomTPE(1, 0, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory(), + new NoOpREHandler()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if keepAliveTime is less than zero + */ + public void testConstructor19() { + try { + new CustomTPE(1, 2, -1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory(), + new NoOpREHandler()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if corePoolSize is greater than the maximumPoolSize + */ + public void testConstructor20() { + try { + new CustomTPE(2, 1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory(), + new NoOpREHandler()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if workQueue is null + */ + public void testConstructorNullPointerException6() { + try { + new CustomTPE(1, 2, 1L, SECONDS, + null, + new SimpleThreadFactory(), + new NoOpREHandler()); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Constructor throws if handler is null + */ + public void testConstructorNullPointerException7() { + try { + new CustomTPE(1, 2, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory(), + (RejectedExecutionHandler) null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Constructor throws if ThreadFactory is null + */ + public void testConstructorNullPointerException8() { + try { + new CustomTPE(1, 2, 1L, SECONDS, + new ArrayBlockingQueue(10), + (ThreadFactory) null, + new NoOpREHandler()); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * execute throws RejectedExecutionException if saturated. + */ + public void testSaturatedExecute() { + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(1)); + try (PoolCleaner cleaner = cleaner(p, done)) { + Runnable task = new CheckedRunnable() { + public void realRun() throws InterruptedException { + await(done); + }}; + for (int i = 0; i < 2; ++i) + p.execute(task); + for (int i = 0; i < 2; ++i) { + try { + p.execute(task); + shouldThrow(); + } catch (RejectedExecutionException success) {} + assertTrue(p.getTaskCount() <= 2); + } + } + } + + /** + * executor using CallerRunsPolicy runs task if saturated. + */ + public void testSaturatedExecute2() { + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(1), + new CustomTPE.CallerRunsPolicy()); + try (PoolCleaner cleaner = cleaner(p, done)) { + Runnable blocker = new CheckedRunnable() { + public void realRun() throws InterruptedException { + await(done); + }}; + p.execute(blocker); + TrackedNoOpRunnable[] tasks = new TrackedNoOpRunnable[5]; + for (int i = 0; i < tasks.length; i++) + tasks[i] = new TrackedNoOpRunnable(); + for (int i = 0; i < tasks.length; i++) + p.execute(tasks[i]); + for (int i = 1; i < tasks.length; i++) + assertTrue(tasks[i].done); + assertFalse(tasks[0].done); // waiting in queue + } + } + + /** + * executor using DiscardPolicy drops task if saturated. + */ + public void testSaturatedExecute3() { + final TrackedNoOpRunnable[] tasks = new TrackedNoOpRunnable[5]; + for (int i = 0; i < tasks.length; ++i) + tasks[i] = new TrackedNoOpRunnable(); + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(1), + new CustomTPE.DiscardPolicy()); + try (PoolCleaner cleaner = cleaner(p, done)) { + p.execute(awaiter(done)); + + for (TrackedNoOpRunnable task : tasks) + p.execute(task); + for (int i = 1; i < tasks.length; i++) + assertFalse(tasks[i].done); + } + for (int i = 1; i < tasks.length; i++) + assertFalse(tasks[i].done); + assertTrue(tasks[0].done); // was waiting in queue + } + + /** + * executor using DiscardOldestPolicy drops oldest task if saturated. + */ + public void testSaturatedExecute4() { + final CountDownLatch done = new CountDownLatch(1); + LatchAwaiter r1 = awaiter(done); + LatchAwaiter r2 = awaiter(done); + LatchAwaiter r3 = awaiter(done); + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(1), + new CustomTPE.DiscardOldestPolicy()); + try (PoolCleaner cleaner = cleaner(p, done)) { + assertEquals(LatchAwaiter.NEW, r1.state); + assertEquals(LatchAwaiter.NEW, r2.state); + assertEquals(LatchAwaiter.NEW, r3.state); + p.execute(r1); + p.execute(r2); + assertTrue(p.getQueue().contains(r2)); + p.execute(r3); + assertFalse(p.getQueue().contains(r2)); + assertTrue(p.getQueue().contains(r3)); + } + assertEquals(LatchAwaiter.DONE, r1.state); + assertEquals(LatchAwaiter.NEW, r2.state); + assertEquals(LatchAwaiter.DONE, r3.state); + } + + /** + * execute throws RejectedExecutionException if shutdown + */ + public void testRejectedExecutionExceptionOnShutdown() { + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(1)); + try { p.shutdown(); } catch (SecurityException ok) { return; } + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.execute(new NoOpRunnable()); + shouldThrow(); + } catch (RejectedExecutionException success) {} + } + } + + /** + * execute using CallerRunsPolicy drops task on shutdown + */ + public void testCallerRunsOnShutdown() { + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(1), + new CustomTPE.CallerRunsPolicy()); + try { p.shutdown(); } catch (SecurityException ok) { return; } + try (PoolCleaner cleaner = cleaner(p)) { + TrackedNoOpRunnable r = new TrackedNoOpRunnable(); + p.execute(r); + assertFalse(r.done); + } + } + + /** + * execute using DiscardPolicy drops task on shutdown + */ + public void testDiscardOnShutdown() { + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(1), + new CustomTPE.DiscardPolicy()); + try { p.shutdown(); } catch (SecurityException ok) { return; } + try (PoolCleaner cleaner = cleaner(p)) { + TrackedNoOpRunnable r = new TrackedNoOpRunnable(); + p.execute(r); + assertFalse(r.done); + } + } + + /** + * execute using DiscardOldestPolicy drops task on shutdown + */ + public void testDiscardOldestOnShutdown() { + final ThreadPoolExecutor p = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(1), + new CustomTPE.DiscardOldestPolicy()); + + try { p.shutdown(); } catch (SecurityException ok) { return; } + try (PoolCleaner cleaner = cleaner(p)) { + TrackedNoOpRunnable r = new TrackedNoOpRunnable(); + p.execute(r); + assertFalse(r.done); + } + } + + /** + * execute(null) throws NPE + */ + public void testExecuteNull() { + final ThreadPoolExecutor p = + new CustomTPE(1, 2, + 1L, SECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.execute(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * setCorePoolSize of negative value throws IllegalArgumentException + */ + public void testCorePoolSizeIllegalArgumentException() { + final ThreadPoolExecutor p = + new CustomTPE(1, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setCorePoolSize(-1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * setMaximumPoolSize(int) throws IllegalArgumentException + * if given a value less the core pool size + */ + public void testMaximumPoolSizeIllegalArgumentException() { + final ThreadPoolExecutor p = + new CustomTPE(2, 3, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setMaximumPoolSize(1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * setMaximumPoolSize throws IllegalArgumentException + * if given a negative value + */ + public void testMaximumPoolSizeIllegalArgumentException2() { + final ThreadPoolExecutor p = + new CustomTPE(2, 3, + LONG_DELAY_MS, + MILLISECONDS,new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setMaximumPoolSize(-1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * setKeepAliveTime throws IllegalArgumentException + * when given a negative value + */ + public void testKeepAliveTimeIllegalArgumentException() { + final ThreadPoolExecutor p = + new CustomTPE(2, 3, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setKeepAliveTime(-1, MILLISECONDS); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * terminated() is called on termination + */ + public void testTerminated() { + CustomTPE p = new CustomTPE(); + try (PoolCleaner cleaner = cleaner(p)) { + try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.terminatedCalled()); + assertTrue(p.isShutdown()); + } + } + + /** + * beforeExecute and afterExecute are called when executing task + */ + public void testBeforeAfter() throws InterruptedException { + CustomTPE p = new CustomTPE(); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch done = new CountDownLatch(1); + p.execute(new CheckedRunnable() { + public void realRun() { + done.countDown(); + }}); + await(p.afterCalled); + assertEquals(0, done.getCount()); + assertTrue(p.afterCalled()); + assertTrue(p.beforeCalled()); + } + } + + /** + * completed submit of callable returns result + */ + public void testSubmitCallable() throws Exception { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + Future future = e.submit(new StringTask()); + String result = future.get(); + assertSame(TEST_STRING, result); + } + } + + /** + * completed submit of runnable returns successfully + */ + public void testSubmitRunnable() throws Exception { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + Future future = e.submit(new NoOpRunnable()); + future.get(); + assertTrue(future.isDone()); + } + } + + /** + * completed submit of (runnable, result) returns result + */ + public void testSubmitRunnable2() throws Exception { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + Future future = e.submit(new NoOpRunnable(), TEST_STRING); + String result = future.get(); + assertSame(TEST_STRING, result); + } + } + + /** + * invokeAny(null) throws NPE + */ + public void testInvokeAny1() throws Exception { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * invokeAny(empty collection) throws IAE + */ + public void testInvokeAny2() throws Exception { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(new ArrayList>()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * invokeAny(c) throws NPE if c has null elements + */ + public void testInvokeAny3() throws Exception { + CountDownLatch latch = new CountDownLatch(1); + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(latchAwaitingStringTask(latch)); + l.add(null); + try { + e.invokeAny(l); + shouldThrow(); + } catch (NullPointerException success) {} + latch.countDown(); + } + } + + /** + * invokeAny(c) throws ExecutionException if no task completes + */ + public void testInvokeAny4() throws Exception { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + try { + e.invokeAny(l); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + } + } + + /** + * invokeAny(c) returns result of some task + */ + public void testInvokeAny5() throws Exception { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(new StringTask()); + String result = e.invokeAny(l); + assertSame(TEST_STRING, result); + } + } + + /** + * invokeAll(null) throws NPE + */ + public void testInvokeAll1() throws Exception { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAll(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * invokeAll(empty collection) returns empty collection + */ + public void testInvokeAll2() throws Exception { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> r = e.invokeAll(new ArrayList>()); + assertTrue(r.isEmpty()); + } + } + + /** + * invokeAll(c) throws NPE if c has null elements + */ + public void testInvokeAll3() throws Exception { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(null); + try { + e.invokeAll(l); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * get of element of invokeAll(c) throws exception on failed task + */ + public void testInvokeAll4() throws Exception { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + List> futures = e.invokeAll(l); + assertEquals(1, futures.size()); + try { + futures.get(0).get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + } + } + + /** + * invokeAll(c) returns results of all completed tasks + */ + public void testInvokeAll5() throws Exception { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(new StringTask()); + List> futures = e.invokeAll(l); + assertEquals(2, futures.size()); + for (Future future : futures) + assertSame(TEST_STRING, future.get()); + } + } + + /** + * timed invokeAny(null) throws NPE + */ + public void testTimedInvokeAny1() throws Exception { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(null, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed invokeAny(,,null) throws NPE + */ + public void testTimedInvokeAnyNullTimeUnit() throws Exception { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + try { + e.invokeAny(l, MEDIUM_DELAY_MS, null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed invokeAny(empty collection) throws IAE + */ + public void testTimedInvokeAny2() throws Exception { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(new ArrayList>(), + MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * timed invokeAny(c) throws NPE if c has null elements + */ + public void testTimedInvokeAny3() throws Exception { + CountDownLatch latch = new CountDownLatch(1); + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(latchAwaitingStringTask(latch)); + l.add(null); + try { + e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + latch.countDown(); + } + } + + /** + * timed invokeAny(c) throws ExecutionException if no task completes + */ + public void testTimedInvokeAny4() throws Exception { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + long startTime = System.nanoTime(); + List> l = new ArrayList>(); + l.add(new NPETask()); + try { + e.invokeAny(l, LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + } + + /** + * timed invokeAny(c) returns result of some task + */ + public void testTimedInvokeAny5() throws Exception { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + long startTime = System.nanoTime(); + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(new StringTask()); + String result = e.invokeAny(l, LONG_DELAY_MS, MILLISECONDS); + assertSame(TEST_STRING, result); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + } + + /** + * timed invokeAll(null) throws NPE + */ + public void testTimedInvokeAll1() throws Exception { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAll(null, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed invokeAll(,,null) throws NPE + */ + public void testTimedInvokeAllNullTimeUnit() throws Exception { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + try { + e.invokeAll(l, MEDIUM_DELAY_MS, null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed invokeAll(empty collection) returns empty collection + */ + public void testTimedInvokeAll2() throws Exception { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> r = e.invokeAll(new ArrayList>(), + MEDIUM_DELAY_MS, MILLISECONDS); + assertTrue(r.isEmpty()); + } + } + + /** + * timed invokeAll(c) throws NPE if c has null elements + */ + public void testTimedInvokeAll3() throws Exception { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(null); + try { + e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * get of element of invokeAll(c) throws exception on failed task + */ + public void testTimedInvokeAll4() throws Exception { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + List> futures = + e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS); + assertEquals(1, futures.size()); + try { + futures.get(0).get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + } + } + + /** + * timed invokeAll(c) returns results of all completed tasks + */ + public void testTimedInvokeAll5() throws Exception { + final ExecutorService e = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(new StringTask()); + List> futures = + e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS); + assertEquals(2, futures.size()); + for (Future future : futures) + assertSame(TEST_STRING, future.get()); + } + } + + /** + * timed invokeAll(c) cancels tasks not completed by timeout + */ + public void testTimedInvokeAll6() throws Exception { + for (long timeout = timeoutMillis();;) { + final CountDownLatch done = new CountDownLatch(1); + final Callable waiter = new CheckedCallable() { + public String realCall() { + try { done.await(LONG_DELAY_MS, MILLISECONDS); } + catch (InterruptedException ok) {} + return "1"; }}; + final ExecutorService p = + new CustomTPE(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p, done)) { + List> tasks = new ArrayList<>(); + tasks.add(new StringTask("0")); + tasks.add(waiter); + tasks.add(new StringTask("2")); + long startTime = System.nanoTime(); + List> futures = + p.invokeAll(tasks, timeout, MILLISECONDS); + assertEquals(tasks.size(), futures.size()); + assertTrue(millisElapsedSince(startTime) >= timeout); + for (Future future : futures) + assertTrue(future.isDone()); + assertTrue(futures.get(1).isCancelled()); + try { + assertEquals("0", futures.get(0).get()); + assertEquals("2", futures.get(2).get()); + break; + } catch (CancellationException retryWithLongerTimeout) { + timeout *= 2; + if (timeout >= LONG_DELAY_MS / 2) + fail("expected exactly one task to be cancelled"); + } + } + } + } + + /** + * Execution continues if there is at least one thread even if + * thread factory fails to create more + */ + public void testFailingThreadFactory() throws InterruptedException { + final ExecutorService e = + new CustomTPE(100, 100, + LONG_DELAY_MS, MILLISECONDS, + new LinkedBlockingQueue(), + new FailingThreadFactory()); + try (PoolCleaner cleaner = cleaner(e)) { + final int TASKS = 100; + final CountDownLatch done = new CountDownLatch(TASKS); + for (int k = 0; k < TASKS; ++k) + e.execute(new CheckedRunnable() { + public void realRun() { + done.countDown(); + }}); + assertTrue(done.await(LONG_DELAY_MS, MILLISECONDS)); + } + } + + /** + * allowsCoreThreadTimeOut is by default false. + */ + public void testAllowsCoreThreadTimeOut() { + final ThreadPoolExecutor p = + new CustomTPE(2, 2, + 1000, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + assertFalse(p.allowsCoreThreadTimeOut()); + } + } + + /** + * allowCoreThreadTimeOut(true) causes idle threads to time out + */ + public void testAllowCoreThreadTimeOut_true() throws Exception { + long keepAliveTime = timeoutMillis(); + final ThreadPoolExecutor p = + new CustomTPE(2, 10, + keepAliveTime, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + p.allowCoreThreadTimeOut(true); + p.execute(new CheckedRunnable() { + public void realRun() { + threadStarted.countDown(); + assertEquals(1, p.getPoolSize()); + }}); + await(threadStarted); + delay(keepAliveTime); + long startTime = System.nanoTime(); + while (p.getPoolSize() > 0 + && millisElapsedSince(startTime) < LONG_DELAY_MS) + Thread.yield(); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + assertEquals(0, p.getPoolSize()); + } + } + + /** + * allowCoreThreadTimeOut(false) causes idle threads not to time out + */ + public void testAllowCoreThreadTimeOut_false() throws Exception { + long keepAliveTime = timeoutMillis(); + final ThreadPoolExecutor p = + new CustomTPE(2, 10, + keepAliveTime, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + p.allowCoreThreadTimeOut(false); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + assertTrue(p.getPoolSize() >= 1); + }}); + delay(2 * keepAliveTime); + assertTrue(p.getPoolSize() >= 1); + } + } + + /** + * get(cancelled task) throws CancellationException + * (in part, a test of CustomTPE itself) + */ + public void testGet_cancelled() throws Exception { + final CountDownLatch done = new CountDownLatch(1); + final ExecutorService e = + new CustomTPE(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new LinkedBlockingQueue()); + try (PoolCleaner cleaner = cleaner(e, done)) { + final CountDownLatch blockerStarted = new CountDownLatch(1); + final List> futures = new ArrayList<>(); + for (int i = 0; i < 2; i++) { + Runnable r = new CheckedRunnable() { public void realRun() + throws Throwable { + blockerStarted.countDown(); + assertTrue(done.await(2 * LONG_DELAY_MS, MILLISECONDS)); + }}; + futures.add(e.submit(r)); + } + await(blockerStarted); + for (Future future : futures) future.cancel(false); + for (Future future : futures) { + try { + future.get(); + shouldThrow(); + } catch (CancellationException success) {} + try { + future.get(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (CancellationException success) {} + assertTrue(future.isCancelled()); + assertTrue(future.isDone()); + } + } + } + +} diff --git a/jdk/test/java/util/concurrent/tck/ThreadPoolExecutorTest.java b/jdk/test/java/util/concurrent/tck/ThreadPoolExecutorTest.java new file mode 100644 index 00000000000..5df3c94b120 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ThreadPoolExecutorTest.java @@ -0,0 +1,2093 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ThreadPoolExecutorTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(ThreadPoolExecutorTest.class); + } + + static class ExtendedTPE extends ThreadPoolExecutor { + final CountDownLatch beforeCalled = new CountDownLatch(1); + final CountDownLatch afterCalled = new CountDownLatch(1); + final CountDownLatch terminatedCalled = new CountDownLatch(1); + + public ExtendedTPE() { + super(1, 1, LONG_DELAY_MS, MILLISECONDS, new SynchronousQueue()); + } + protected void beforeExecute(Thread t, Runnable r) { + beforeCalled.countDown(); + } + protected void afterExecute(Runnable r, Throwable t) { + afterCalled.countDown(); + } + protected void terminated() { + terminatedCalled.countDown(); + } + + public boolean beforeCalled() { + return beforeCalled.getCount() == 0; + } + public boolean afterCalled() { + return afterCalled.getCount() == 0; + } + public boolean terminatedCalled() { + return terminatedCalled.getCount() == 0; + } + } + + static class FailingThreadFactory implements ThreadFactory { + int calls = 0; + public Thread newThread(Runnable r) { + if (++calls > 1) return null; + return new Thread(r); + } + } + + /** + * execute successfully executes a runnable + */ + public void testExecute() throws InterruptedException { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch done = new CountDownLatch(1); + final Runnable task = new CheckedRunnable() { + public void realRun() { done.countDown(); }}; + p.execute(task); + assertTrue(done.await(LONG_DELAY_MS, MILLISECONDS)); + } + } + + /** + * getActiveCount increases but doesn't overestimate, when a + * thread becomes active + */ + public void testGetActiveCount() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + assertEquals(0, p.getActiveCount()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + assertEquals(1, p.getActiveCount()); + await(done); + }}); + await(threadStarted); + assertEquals(1, p.getActiveCount()); + } + } + + /** + * prestartCoreThread starts a thread if under corePoolSize, else doesn't + */ + public void testPrestartCoreThread() { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(2, 6, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(0, p.getPoolSize()); + assertTrue(p.prestartCoreThread()); + assertEquals(1, p.getPoolSize()); + assertTrue(p.prestartCoreThread()); + assertEquals(2, p.getPoolSize()); + assertFalse(p.prestartCoreThread()); + assertEquals(2, p.getPoolSize()); + p.setCorePoolSize(4); + assertTrue(p.prestartCoreThread()); + assertEquals(3, p.getPoolSize()); + assertTrue(p.prestartCoreThread()); + assertEquals(4, p.getPoolSize()); + assertFalse(p.prestartCoreThread()); + assertEquals(4, p.getPoolSize()); + } + } + + /** + * prestartAllCoreThreads starts all corePoolSize threads + */ + public void testPrestartAllCoreThreads() { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(2, 6, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(0, p.getPoolSize()); + p.prestartAllCoreThreads(); + assertEquals(2, p.getPoolSize()); + p.prestartAllCoreThreads(); + assertEquals(2, p.getPoolSize()); + p.setCorePoolSize(4); + p.prestartAllCoreThreads(); + assertEquals(4, p.getPoolSize()); + p.prestartAllCoreThreads(); + assertEquals(4, p.getPoolSize()); + } + } + + /** + * getCompletedTaskCount increases, but doesn't overestimate, + * when tasks complete + */ + public void testGetCompletedTaskCount() throws InterruptedException { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + final CountDownLatch threadProceed = new CountDownLatch(1); + final CountDownLatch threadDone = new CountDownLatch(1); + assertEquals(0, p.getCompletedTaskCount()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + assertEquals(0, p.getCompletedTaskCount()); + threadProceed.await(); + threadDone.countDown(); + }}); + await(threadStarted); + assertEquals(0, p.getCompletedTaskCount()); + threadProceed.countDown(); + threadDone.await(); + long startTime = System.nanoTime(); + while (p.getCompletedTaskCount() != 1) { + if (millisElapsedSince(startTime) > LONG_DELAY_MS) + fail("timed out"); + Thread.yield(); + } + } + } + + /** + * getCorePoolSize returns size given in constructor if not otherwise set + */ + public void testGetCorePoolSize() { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(1, p.getCorePoolSize()); + } + } + + /** + * getKeepAliveTime returns value given in constructor if not otherwise set + */ + public void testGetKeepAliveTime() { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(2, 2, + 1000, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(1, p.getKeepAliveTime(SECONDS)); + } + } + + /** + * getThreadFactory returns factory in constructor if not set + */ + public void testGetThreadFactory() { + ThreadFactory threadFactory = new SimpleThreadFactory(); + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10), + threadFactory, + new NoOpREHandler()); + try (PoolCleaner cleaner = cleaner(p)) { + assertSame(threadFactory, p.getThreadFactory()); + } + } + + /** + * setThreadFactory sets the thread factory returned by getThreadFactory + */ + public void testSetThreadFactory() { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + ThreadFactory threadFactory = new SimpleThreadFactory(); + p.setThreadFactory(threadFactory); + assertSame(threadFactory, p.getThreadFactory()); + } + } + + /** + * setThreadFactory(null) throws NPE + */ + public void testSetThreadFactoryNull() { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setThreadFactory(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * getRejectedExecutionHandler returns handler in constructor if not set + */ + public void testGetRejectedExecutionHandler() { + final RejectedExecutionHandler handler = new NoOpREHandler(); + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10), + handler); + try (PoolCleaner cleaner = cleaner(p)) { + assertSame(handler, p.getRejectedExecutionHandler()); + } + } + + /** + * setRejectedExecutionHandler sets the handler returned by + * getRejectedExecutionHandler + */ + public void testSetRejectedExecutionHandler() { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + RejectedExecutionHandler handler = new NoOpREHandler(); + p.setRejectedExecutionHandler(handler); + assertSame(handler, p.getRejectedExecutionHandler()); + } + } + + /** + * setRejectedExecutionHandler(null) throws NPE + */ + public void testSetRejectedExecutionHandlerNull() { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setRejectedExecutionHandler(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * getLargestPoolSize increases, but doesn't overestimate, when + * multiple threads active + */ + public void testGetLargestPoolSize() throws InterruptedException { + final int THREADS = 3; + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = + new ThreadPoolExecutor(THREADS, THREADS, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p, done)) { + assertEquals(0, p.getLargestPoolSize()); + final CountDownLatch threadsStarted = new CountDownLatch(THREADS); + for (int i = 0; i < THREADS; i++) + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadsStarted.countDown(); + await(done); + assertEquals(THREADS, p.getLargestPoolSize()); + }}); + await(threadsStarted); + assertEquals(THREADS, p.getLargestPoolSize()); + } + assertEquals(THREADS, p.getLargestPoolSize()); + } + + /** + * getMaximumPoolSize returns value given in constructor if not + * otherwise set + */ + public void testGetMaximumPoolSize() { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(2, 3, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + assertEquals(3, p.getMaximumPoolSize()); + p.setMaximumPoolSize(5); + assertEquals(5, p.getMaximumPoolSize()); + p.setMaximumPoolSize(4); + assertEquals(4, p.getMaximumPoolSize()); + } + } + + /** + * getPoolSize increases, but doesn't overestimate, when threads + * become active + */ + public void testGetPoolSize() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p, done)) { + assertEquals(0, p.getPoolSize()); + final CountDownLatch threadStarted = new CountDownLatch(1); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + assertEquals(1, p.getPoolSize()); + await(done); + }}); + await(threadStarted); + assertEquals(1, p.getPoolSize()); + } + } + + /** + * getTaskCount increases, but doesn't overestimate, when tasks submitted + */ + public void testGetTaskCount() throws InterruptedException { + final int TASKS = 3; + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + assertEquals(0, p.getTaskCount()); + assertEquals(0, p.getCompletedTaskCount()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + await(done); + }}); + await(threadStarted); + assertEquals(1, p.getTaskCount()); + assertEquals(0, p.getCompletedTaskCount()); + for (int i = 0; i < TASKS; i++) { + assertEquals(1 + i, p.getTaskCount()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + assertEquals(1 + TASKS, p.getTaskCount()); + await(done); + }}); + } + assertEquals(1 + TASKS, p.getTaskCount()); + assertEquals(0, p.getCompletedTaskCount()); + } + assertEquals(1 + TASKS, p.getTaskCount()); + assertEquals(1 + TASKS, p.getCompletedTaskCount()); + } + + /** + * isShutdown is false before shutdown, true after + */ + public void testIsShutdown() { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + assertFalse(p.isShutdown()); + try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.isShutdown()); + } + } + + /** + * awaitTermination on a non-shutdown pool times out + */ + public void testAwaitTermination_timesOut() throws InterruptedException { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + assertFalse(p.isTerminated()); + assertFalse(p.awaitTermination(Long.MIN_VALUE, NANOSECONDS)); + assertFalse(p.awaitTermination(Long.MIN_VALUE, MILLISECONDS)); + assertFalse(p.awaitTermination(-1L, NANOSECONDS)); + assertFalse(p.awaitTermination(-1L, MILLISECONDS)); + assertFalse(p.awaitTermination(0L, NANOSECONDS)); + assertFalse(p.awaitTermination(0L, MILLISECONDS)); + long timeoutNanos = 999999L; + long startTime = System.nanoTime(); + assertFalse(p.awaitTermination(timeoutNanos, NANOSECONDS)); + assertTrue(System.nanoTime() - startTime >= timeoutNanos); + assertFalse(p.isTerminated()); + startTime = System.nanoTime(); + long timeoutMillis = timeoutMillis(); + assertFalse(p.awaitTermination(timeoutMillis, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + assertFalse(p.isTerminated()); + try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + } + } + + /** + * isTerminated is false before termination, true after + */ + public void testIsTerminated() throws InterruptedException { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + final CountDownLatch done = new CountDownLatch(1); + assertFalse(p.isTerminating()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(p.isTerminating()); + threadStarted.countDown(); + await(done); + }}); + await(threadStarted); + assertFalse(p.isTerminating()); + done.countDown(); + try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + assertFalse(p.isTerminating()); + } + } + + /** + * isTerminating is not true when running or when terminated + */ + public void testIsTerminating() throws InterruptedException { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + final CountDownLatch done = new CountDownLatch(1); + assertFalse(p.isTerminating()); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + assertFalse(p.isTerminating()); + threadStarted.countDown(); + await(done); + }}); + await(threadStarted); + assertFalse(p.isTerminating()); + done.countDown(); + try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + assertFalse(p.isTerminating()); + } + } + + /** + * getQueue returns the work queue, which contains queued tasks + */ + public void testGetQueue() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); + final BlockingQueue q = new ArrayBlockingQueue(10); + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 1, + LONG_DELAY_MS, MILLISECONDS, + q); + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + FutureTask[] tasks = new FutureTask[5]; + for (int i = 0; i < tasks.length; i++) { + Callable task = new CheckedCallable() { + public Boolean realCall() throws InterruptedException { + threadStarted.countDown(); + assertSame(q, p.getQueue()); + await(done); + return Boolean.TRUE; + }}; + tasks[i] = new FutureTask(task); + p.execute(tasks[i]); + } + await(threadStarted); + assertSame(q, p.getQueue()); + assertFalse(q.contains(tasks[0])); + assertTrue(q.contains(tasks[tasks.length - 1])); + assertEquals(tasks.length - 1, q.size()); + } + } + + /** + * remove(task) removes queued task, and fails to remove active task + */ + public void testRemove() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); + BlockingQueue q = new ArrayBlockingQueue(10); + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 1, + LONG_DELAY_MS, MILLISECONDS, + q); + try (PoolCleaner cleaner = cleaner(p, done)) { + Runnable[] tasks = new Runnable[6]; + final CountDownLatch threadStarted = new CountDownLatch(1); + for (int i = 0; i < tasks.length; i++) { + tasks[i] = new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + await(done); + }}; + p.execute(tasks[i]); + } + await(threadStarted); + assertFalse(p.remove(tasks[0])); + assertTrue(q.contains(tasks[4])); + assertTrue(q.contains(tasks[3])); + assertTrue(p.remove(tasks[4])); + assertFalse(p.remove(tasks[4])); + assertFalse(q.contains(tasks[4])); + assertTrue(q.contains(tasks[3])); + assertTrue(p.remove(tasks[3])); + assertFalse(q.contains(tasks[3])); + } + } + + /** + * purge removes cancelled tasks from the queue + */ + public void testPurge() throws InterruptedException { + final CountDownLatch threadStarted = new CountDownLatch(1); + final CountDownLatch done = new CountDownLatch(1); + final BlockingQueue q = new ArrayBlockingQueue(10); + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 1, + LONG_DELAY_MS, MILLISECONDS, + q); + try (PoolCleaner cleaner = cleaner(p, done)) { + FutureTask[] tasks = new FutureTask[5]; + for (int i = 0; i < tasks.length; i++) { + Callable task = new CheckedCallable() { + public Boolean realCall() throws InterruptedException { + threadStarted.countDown(); + await(done); + return Boolean.TRUE; + }}; + tasks[i] = new FutureTask(task); + p.execute(tasks[i]); + } + await(threadStarted); + assertEquals(tasks.length, p.getTaskCount()); + assertEquals(tasks.length - 1, q.size()); + assertEquals(1L, p.getActiveCount()); + assertEquals(0L, p.getCompletedTaskCount()); + tasks[4].cancel(true); + tasks[3].cancel(false); + p.purge(); + assertEquals(tasks.length - 3, q.size()); + assertEquals(tasks.length - 2, p.getTaskCount()); + p.purge(); // Nothing to do + assertEquals(tasks.length - 3, q.size()); + assertEquals(tasks.length - 2, p.getTaskCount()); + } + } + + /** + * shutdownNow returns a list containing tasks that were not run, + * and those tasks are drained from the queue + */ + public void testShutdownNow() throws InterruptedException { + final int poolSize = 2; + final int count = 5; + final AtomicInteger ran = new AtomicInteger(0); + final ThreadPoolExecutor p = + new ThreadPoolExecutor(poolSize, poolSize, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + final CountDownLatch threadsStarted = new CountDownLatch(poolSize); + Runnable waiter = new CheckedRunnable() { public void realRun() { + threadsStarted.countDown(); + try { + MILLISECONDS.sleep(2 * LONG_DELAY_MS); + } catch (InterruptedException success) {} + ran.getAndIncrement(); + }}; + for (int i = 0; i < count; i++) + p.execute(waiter); + await(threadsStarted); + assertEquals(poolSize, p.getActiveCount()); + assertEquals(0, p.getCompletedTaskCount()); + final List queuedTasks; + try { + queuedTasks = p.shutdownNow(); + } catch (SecurityException ok) { + return; // Allowed in case test doesn't have privs + } + assertTrue(p.isShutdown()); + assertTrue(p.getQueue().isEmpty()); + assertEquals(count - poolSize, queuedTasks.size()); + assertTrue(p.awaitTermination(LONG_DELAY_MS, MILLISECONDS)); + assertTrue(p.isTerminated()); + assertEquals(poolSize, ran.get()); + assertEquals(poolSize, p.getCompletedTaskCount()); + } + + // Exception Tests + + /** + * Constructor throws if corePoolSize argument is less than zero + */ + public void testConstructor1() { + try { + new ThreadPoolExecutor(-1, 1, 1L, SECONDS, + new ArrayBlockingQueue(10)); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if maximumPoolSize is less than zero + */ + public void testConstructor2() { + try { + new ThreadPoolExecutor(1, -1, 1L, SECONDS, + new ArrayBlockingQueue(10)); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if maximumPoolSize is equal to zero + */ + public void testConstructor3() { + try { + new ThreadPoolExecutor(1, 0, 1L, SECONDS, + new ArrayBlockingQueue(10)); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if keepAliveTime is less than zero + */ + public void testConstructor4() { + try { + new ThreadPoolExecutor(1, 2, -1L, SECONDS, + new ArrayBlockingQueue(10)); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if corePoolSize is greater than the maximumPoolSize + */ + public void testConstructor5() { + try { + new ThreadPoolExecutor(2, 1, 1L, SECONDS, + new ArrayBlockingQueue(10)); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if workQueue is set to null + */ + public void testConstructorNullPointerException() { + try { + new ThreadPoolExecutor(1, 2, 1L, SECONDS, + (BlockingQueue) null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Constructor throws if corePoolSize argument is less than zero + */ + public void testConstructor6() { + try { + new ThreadPoolExecutor(-1, 1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if maximumPoolSize is less than zero + */ + public void testConstructor7() { + try { + new ThreadPoolExecutor(1, -1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if maximumPoolSize is equal to zero + */ + public void testConstructor8() { + try { + new ThreadPoolExecutor(1, 0, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if keepAliveTime is less than zero + */ + public void testConstructor9() { + try { + new ThreadPoolExecutor(1, 2, -1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if corePoolSize is greater than the maximumPoolSize + */ + public void testConstructor10() { + try { + new ThreadPoolExecutor(2, 1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if workQueue is set to null + */ + public void testConstructorNullPointerException2() { + try { + new ThreadPoolExecutor(1, 2, 1L, SECONDS, + (BlockingQueue) null, + new SimpleThreadFactory()); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Constructor throws if threadFactory is set to null + */ + public void testConstructorNullPointerException3() { + try { + new ThreadPoolExecutor(1, 2, 1L, SECONDS, + new ArrayBlockingQueue(10), + (ThreadFactory) null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Constructor throws if corePoolSize argument is less than zero + */ + public void testConstructor11() { + try { + new ThreadPoolExecutor(-1, 1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new NoOpREHandler()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if maximumPoolSize is less than zero + */ + public void testConstructor12() { + try { + new ThreadPoolExecutor(1, -1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new NoOpREHandler()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if maximumPoolSize is equal to zero + */ + public void testConstructor13() { + try { + new ThreadPoolExecutor(1, 0, 1L, SECONDS, + new ArrayBlockingQueue(10), + new NoOpREHandler()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if keepAliveTime is less than zero + */ + public void testConstructor14() { + try { + new ThreadPoolExecutor(1, 2, -1L, SECONDS, + new ArrayBlockingQueue(10), + new NoOpREHandler()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if corePoolSize is greater than the maximumPoolSize + */ + public void testConstructor15() { + try { + new ThreadPoolExecutor(2, 1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new NoOpREHandler()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if workQueue is set to null + */ + public void testConstructorNullPointerException4() { + try { + new ThreadPoolExecutor(1, 2, 1L, SECONDS, + (BlockingQueue) null, + new NoOpREHandler()); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Constructor throws if handler is set to null + */ + public void testConstructorNullPointerException5() { + try { + new ThreadPoolExecutor(1, 2, 1L, SECONDS, + new ArrayBlockingQueue(10), + (RejectedExecutionHandler) null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Constructor throws if corePoolSize argument is less than zero + */ + public void testConstructor16() { + try { + new ThreadPoolExecutor(-1, 1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory(), + new NoOpREHandler()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if maximumPoolSize is less than zero + */ + public void testConstructor17() { + try { + new ThreadPoolExecutor(1, -1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory(), + new NoOpREHandler()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if maximumPoolSize is equal to zero + */ + public void testConstructor18() { + try { + new ThreadPoolExecutor(1, 0, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory(), + new NoOpREHandler()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if keepAliveTime is less than zero + */ + public void testConstructor19() { + try { + new ThreadPoolExecutor(1, 2, -1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory(), + new NoOpREHandler()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if corePoolSize is greater than the maximumPoolSize + */ + public void testConstructor20() { + try { + new ThreadPoolExecutor(2, 1, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory(), + new NoOpREHandler()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + + /** + * Constructor throws if workQueue is null + */ + public void testConstructorNullPointerException6() { + try { + new ThreadPoolExecutor(1, 2, 1L, SECONDS, + (BlockingQueue) null, + new SimpleThreadFactory(), + new NoOpREHandler()); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Constructor throws if handler is null + */ + public void testConstructorNullPointerException7() { + try { + new ThreadPoolExecutor(1, 2, 1L, SECONDS, + new ArrayBlockingQueue(10), + new SimpleThreadFactory(), + (RejectedExecutionHandler) null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Constructor throws if ThreadFactory is null + */ + public void testConstructorNullPointerException8() { + try { + new ThreadPoolExecutor(1, 2, 1L, SECONDS, + new ArrayBlockingQueue(10), + (ThreadFactory) null, + new NoOpREHandler()); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * get of submitted callable throws InterruptedException if interrupted + */ + public void testInterruptedSubmit() throws InterruptedException { + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 1, + 60, SECONDS, + new ArrayBlockingQueue(10)); + + try (PoolCleaner cleaner = cleaner(p, done)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws Exception { + Callable task = new CheckedCallable() { + public Boolean realCall() throws InterruptedException { + threadStarted.countDown(); + await(done); + return Boolean.TRUE; + }}; + p.submit(task).get(); + }}); + + await(threadStarted); + t.interrupt(); + awaitTermination(t); + } + } + + /** + * execute throws RejectedExecutionException if saturated. + */ + public void testSaturatedExecute() { + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(1)); + try (PoolCleaner cleaner = cleaner(p, done)) { + Runnable task = new CheckedRunnable() { + public void realRun() throws InterruptedException { + await(done); + }}; + for (int i = 0; i < 2; ++i) + p.execute(task); + for (int i = 0; i < 2; ++i) { + try { + p.execute(task); + shouldThrow(); + } catch (RejectedExecutionException success) {} + assertTrue(p.getTaskCount() <= 2); + } + } + } + + /** + * submit(runnable) throws RejectedExecutionException if saturated. + */ + public void testSaturatedSubmitRunnable() { + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(1)); + try (PoolCleaner cleaner = cleaner(p, done)) { + Runnable task = new CheckedRunnable() { + public void realRun() throws InterruptedException { + await(done); + }}; + for (int i = 0; i < 2; ++i) + p.submit(task); + for (int i = 0; i < 2; ++i) { + try { + p.execute(task); + shouldThrow(); + } catch (RejectedExecutionException success) {} + assertTrue(p.getTaskCount() <= 2); + } + } + } + + /** + * submit(callable) throws RejectedExecutionException if saturated. + */ + public void testSaturatedSubmitCallable() { + final CountDownLatch done = new CountDownLatch(1); + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(1)); + try (PoolCleaner cleaner = cleaner(p, done)) { + Runnable task = new CheckedRunnable() { + public void realRun() throws InterruptedException { + await(done); + }}; + for (int i = 0; i < 2; ++i) + p.submit(Executors.callable(task)); + for (int i = 0; i < 2; ++i) { + try { + p.execute(task); + shouldThrow(); + } catch (RejectedExecutionException success) {} + assertTrue(p.getTaskCount() <= 2); + } + } + } + + /** + * executor using CallerRunsPolicy runs task if saturated. + */ + public void testSaturatedExecute2() { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 1, + LONG_DELAY_MS, + MILLISECONDS, + new ArrayBlockingQueue(1), + new ThreadPoolExecutor.CallerRunsPolicy()); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch done = new CountDownLatch(1); + Runnable blocker = new CheckedRunnable() { + public void realRun() throws InterruptedException { + await(done); + }}; + p.execute(blocker); + TrackedNoOpRunnable[] tasks = new TrackedNoOpRunnable[5]; + for (int i = 0; i < tasks.length; i++) + tasks[i] = new TrackedNoOpRunnable(); + for (int i = 0; i < tasks.length; i++) + p.execute(tasks[i]); + for (int i = 1; i < tasks.length; i++) + assertTrue(tasks[i].done); + assertFalse(tasks[0].done); // waiting in queue + done.countDown(); + } + } + + /** + * executor using DiscardPolicy drops task if saturated. + */ + public void testSaturatedExecute3() { + final CountDownLatch done = new CountDownLatch(1); + final TrackedNoOpRunnable[] tasks = new TrackedNoOpRunnable[5]; + for (int i = 0; i < tasks.length; ++i) + tasks[i] = new TrackedNoOpRunnable(); + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(1), + new ThreadPoolExecutor.DiscardPolicy()); + try (PoolCleaner cleaner = cleaner(p, done)) { + p.execute(awaiter(done)); + + for (TrackedNoOpRunnable task : tasks) + p.execute(task); + for (int i = 1; i < tasks.length; i++) + assertFalse(tasks[i].done); + } + for (int i = 1; i < tasks.length; i++) + assertFalse(tasks[i].done); + assertTrue(tasks[0].done); // was waiting in queue + } + + /** + * executor using DiscardOldestPolicy drops oldest task if saturated. + */ + public void testSaturatedExecute4() { + final CountDownLatch done = new CountDownLatch(1); + LatchAwaiter r1 = awaiter(done); + LatchAwaiter r2 = awaiter(done); + LatchAwaiter r3 = awaiter(done); + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(1), + new ThreadPoolExecutor.DiscardOldestPolicy()); + try (PoolCleaner cleaner = cleaner(p, done)) { + assertEquals(LatchAwaiter.NEW, r1.state); + assertEquals(LatchAwaiter.NEW, r2.state); + assertEquals(LatchAwaiter.NEW, r3.state); + p.execute(r1); + p.execute(r2); + assertTrue(p.getQueue().contains(r2)); + p.execute(r3); + assertFalse(p.getQueue().contains(r2)); + assertTrue(p.getQueue().contains(r3)); + } + assertEquals(LatchAwaiter.DONE, r1.state); + assertEquals(LatchAwaiter.NEW, r2.state); + assertEquals(LatchAwaiter.DONE, r3.state); + } + + /** + * execute throws RejectedExecutionException if shutdown + */ + public void testRejectedExecutionExceptionOnShutdown() { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(1)); + try { p.shutdown(); } catch (SecurityException ok) { return; } + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.execute(new NoOpRunnable()); + shouldThrow(); + } catch (RejectedExecutionException success) {} + } + } + + /** + * execute using CallerRunsPolicy drops task on shutdown + */ + public void testCallerRunsOnShutdown() { + RejectedExecutionHandler h = new ThreadPoolExecutor.CallerRunsPolicy(); + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(1), h); + + try { p.shutdown(); } catch (SecurityException ok) { return; } + try (PoolCleaner cleaner = cleaner(p)) { + TrackedNoOpRunnable r = new TrackedNoOpRunnable(); + p.execute(r); + assertFalse(r.done); + } + } + + /** + * execute using DiscardPolicy drops task on shutdown + */ + public void testDiscardOnShutdown() { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(1), + new ThreadPoolExecutor.DiscardPolicy()); + + try { p.shutdown(); } catch (SecurityException ok) { return; } + try (PoolCleaner cleaner = cleaner(p)) { + TrackedNoOpRunnable r = new TrackedNoOpRunnable(); + p.execute(r); + assertFalse(r.done); + } + } + + /** + * execute using DiscardOldestPolicy drops task on shutdown + */ + public void testDiscardOldestOnShutdown() { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(1), + new ThreadPoolExecutor.DiscardOldestPolicy()); + + try { p.shutdown(); } catch (SecurityException ok) { return; } + try (PoolCleaner cleaner = cleaner(p)) { + TrackedNoOpRunnable r = new TrackedNoOpRunnable(); + p.execute(r); + assertFalse(r.done); + } + } + + /** + * execute(null) throws NPE + */ + public void testExecuteNull() { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 2, + 1L, SECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.execute(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * setCorePoolSize of negative value throws IllegalArgumentException + */ + public void testCorePoolSizeIllegalArgumentException() { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setCorePoolSize(-1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * setMaximumPoolSize(int) throws IllegalArgumentException if + * given a value less the core pool size + */ + public void testMaximumPoolSizeIllegalArgumentException() { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(2, 3, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setMaximumPoolSize(1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * setMaximumPoolSize throws IllegalArgumentException + * if given a negative value + */ + public void testMaximumPoolSizeIllegalArgumentException2() { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(2, 3, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setMaximumPoolSize(-1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * Configuration changes that allow core pool size greater than + * max pool size result in IllegalArgumentException. + */ + public void testPoolSizeInvariants() { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + for (int s = 1; s < 5; s++) { + p.setMaximumPoolSize(s); + p.setCorePoolSize(s); + try { + p.setMaximumPoolSize(s - 1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + assertEquals(s, p.getCorePoolSize()); + assertEquals(s, p.getMaximumPoolSize()); + try { + p.setCorePoolSize(s + 1); + shouldThrow(); + } catch (IllegalArgumentException success) {} + assertEquals(s, p.getCorePoolSize()); + assertEquals(s, p.getMaximumPoolSize()); + } + } + } + + /** + * setKeepAliveTime throws IllegalArgumentException + * when given a negative value + */ + public void testKeepAliveTimeIllegalArgumentException() { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(2, 3, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + try { + p.setKeepAliveTime(-1, MILLISECONDS); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * terminated() is called on termination + */ + public void testTerminated() { + ExtendedTPE p = new ExtendedTPE(); + try (PoolCleaner cleaner = cleaner(p)) { + try { p.shutdown(); } catch (SecurityException ok) { return; } + assertTrue(p.terminatedCalled()); + assertTrue(p.isShutdown()); + } + } + + /** + * beforeExecute and afterExecute are called when executing task + */ + public void testBeforeAfter() throws InterruptedException { + ExtendedTPE p = new ExtendedTPE(); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch done = new CountDownLatch(1); + p.execute(new CheckedRunnable() { + public void realRun() { + done.countDown(); + }}); + await(p.afterCalled); + assertEquals(0, done.getCount()); + assertTrue(p.afterCalled()); + assertTrue(p.beforeCalled()); + } + } + + /** + * completed submit of callable returns result + */ + public void testSubmitCallable() throws Exception { + final ExecutorService e = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + Future future = e.submit(new StringTask()); + String result = future.get(); + assertSame(TEST_STRING, result); + } + } + + /** + * completed submit of runnable returns successfully + */ + public void testSubmitRunnable() throws Exception { + final ExecutorService e = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + Future future = e.submit(new NoOpRunnable()); + future.get(); + assertTrue(future.isDone()); + } + } + + /** + * completed submit of (runnable, result) returns result + */ + public void testSubmitRunnable2() throws Exception { + final ExecutorService e = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + Future future = e.submit(new NoOpRunnable(), TEST_STRING); + String result = future.get(); + assertSame(TEST_STRING, result); + } + } + + /** + * invokeAny(null) throws NPE + */ + public void testInvokeAny1() throws Exception { + final ExecutorService e = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * invokeAny(empty collection) throws IAE + */ + public void testInvokeAny2() throws Exception { + final ExecutorService e = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(new ArrayList>()); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * invokeAny(c) throws NPE if c has null elements + */ + public void testInvokeAny3() throws Exception { + final CountDownLatch latch = new CountDownLatch(1); + final ExecutorService e = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(latchAwaitingStringTask(latch)); + l.add(null); + try { + e.invokeAny(l); + shouldThrow(); + } catch (NullPointerException success) {} + latch.countDown(); + } + } + + /** + * invokeAny(c) throws ExecutionException if no task completes + */ + public void testInvokeAny4() throws Exception { + final ExecutorService e = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + try { + e.invokeAny(l); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + } + } + + /** + * invokeAny(c) returns result of some task + */ + public void testInvokeAny5() throws Exception { + final ExecutorService e = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(new StringTask()); + String result = e.invokeAny(l); + assertSame(TEST_STRING, result); + } + } + + /** + * invokeAll(null) throws NPE + */ + public void testInvokeAll1() throws Exception { + final ExecutorService e = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAll(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * invokeAll(empty collection) returns empty collection + */ + public void testInvokeAll2() throws InterruptedException { + final ExecutorService e = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> r = e.invokeAll(new ArrayList>()); + assertTrue(r.isEmpty()); + } + } + + /** + * invokeAll(c) throws NPE if c has null elements + */ + public void testInvokeAll3() throws Exception { + final ExecutorService e = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(null); + try { + e.invokeAll(l); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * get of element of invokeAll(c) throws exception on failed task + */ + public void testInvokeAll4() throws Exception { + final ExecutorService e = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + List> futures = e.invokeAll(l); + assertEquals(1, futures.size()); + try { + futures.get(0).get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + } + } + + /** + * invokeAll(c) returns results of all completed tasks + */ + public void testInvokeAll5() throws Exception { + final ExecutorService e = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(new StringTask()); + List> futures = e.invokeAll(l); + assertEquals(2, futures.size()); + for (Future future : futures) + assertSame(TEST_STRING, future.get()); + } + } + + /** + * timed invokeAny(null) throws NPE + */ + public void testTimedInvokeAny1() throws Exception { + final ExecutorService e = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(null, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed invokeAny(,,null) throws NPE + */ + public void testTimedInvokeAnyNullTimeUnit() throws Exception { + final ExecutorService e = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + try { + e.invokeAny(l, MEDIUM_DELAY_MS, null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed invokeAny(empty collection) throws IAE + */ + public void testTimedInvokeAny2() throws Exception { + final ExecutorService e = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAny(new ArrayList>(), + MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + + /** + * timed invokeAny(c) throws NPE if c has null elements + */ + public void testTimedInvokeAny3() throws Exception { + final CountDownLatch latch = new CountDownLatch(1); + final ExecutorService e = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(latchAwaitingStringTask(latch)); + l.add(null); + try { + e.invokeAny(l, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + latch.countDown(); + } + } + + /** + * timed invokeAny(c) throws ExecutionException if no task completes + */ + public void testTimedInvokeAny4() throws Exception { + final ExecutorService e = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + long startTime = System.nanoTime(); + List> l = new ArrayList>(); + l.add(new NPETask()); + try { + e.invokeAny(l, LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + } + + /** + * timed invokeAny(c) returns result of some task + */ + public void testTimedInvokeAny5() throws Exception { + final ExecutorService e = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + long startTime = System.nanoTime(); + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(new StringTask()); + String result = e.invokeAny(l, LONG_DELAY_MS, MILLISECONDS); + assertSame(TEST_STRING, result); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + } + } + + /** + * timed invokeAll(null) throws NPE + */ + public void testTimedInvokeAll1() throws Exception { + final ExecutorService e = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + try { + e.invokeAll(null, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed invokeAll(,,null) throws NPE + */ + public void testTimedInvokeAllNullTimeUnit() throws Exception { + final ExecutorService e = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + try { + e.invokeAll(l, MEDIUM_DELAY_MS, null); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * timed invokeAll(empty collection) returns empty collection + */ + public void testTimedInvokeAll2() throws InterruptedException { + final ExecutorService e = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> r = e.invokeAll(new ArrayList>(), + MEDIUM_DELAY_MS, MILLISECONDS); + assertTrue(r.isEmpty()); + } + } + + /** + * timed invokeAll(c) throws NPE if c has null elements + */ + public void testTimedInvokeAll3() throws Exception { + final ExecutorService e = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(null); + try { + e.invokeAll(l, MEDIUM_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (NullPointerException success) {} + } + } + + /** + * get of element of invokeAll(c) throws exception on failed task + */ + public void testTimedInvokeAll4() throws Exception { + final ExecutorService e = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new NPETask()); + List> futures = + e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS); + assertEquals(1, futures.size()); + try { + futures.get(0).get(); + shouldThrow(); + } catch (ExecutionException success) { + assertTrue(success.getCause() instanceof NullPointerException); + } + } + } + + /** + * timed invokeAll(c) returns results of all completed tasks + */ + public void testTimedInvokeAll5() throws Exception { + final ExecutorService e = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(e)) { + List> l = new ArrayList>(); + l.add(new StringTask()); + l.add(new StringTask()); + List> futures = + e.invokeAll(l, LONG_DELAY_MS, MILLISECONDS); + assertEquals(2, futures.size()); + for (Future future : futures) + assertSame(TEST_STRING, future.get()); + } + } + + /** + * timed invokeAll(c) cancels tasks not completed by timeout + */ + public void testTimedInvokeAll6() throws Exception { + for (long timeout = timeoutMillis();;) { + final CountDownLatch done = new CountDownLatch(1); + final Callable waiter = new CheckedCallable() { + public String realCall() { + try { done.await(LONG_DELAY_MS, MILLISECONDS); } + catch (InterruptedException ok) {} + return "1"; }}; + final ExecutorService p = + new ThreadPoolExecutor(2, 2, + LONG_DELAY_MS, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p, done)) { + List> tasks = new ArrayList<>(); + tasks.add(new StringTask("0")); + tasks.add(waiter); + tasks.add(new StringTask("2")); + long startTime = System.nanoTime(); + List> futures = + p.invokeAll(tasks, timeout, MILLISECONDS); + assertEquals(tasks.size(), futures.size()); + assertTrue(millisElapsedSince(startTime) >= timeout); + for (Future future : futures) + assertTrue(future.isDone()); + assertTrue(futures.get(1).isCancelled()); + try { + assertEquals("0", futures.get(0).get()); + assertEquals("2", futures.get(2).get()); + break; + } catch (CancellationException retryWithLongerTimeout) { + timeout *= 2; + if (timeout >= LONG_DELAY_MS / 2) + fail("expected exactly one task to be cancelled"); + } + } + } + } + + /** + * Execution continues if there is at least one thread even if + * thread factory fails to create more + */ + public void testFailingThreadFactory() throws InterruptedException { + final ExecutorService e = + new ThreadPoolExecutor(100, 100, + LONG_DELAY_MS, MILLISECONDS, + new LinkedBlockingQueue(), + new FailingThreadFactory()); + try (PoolCleaner cleaner = cleaner(e)) { + final int TASKS = 100; + final CountDownLatch done = new CountDownLatch(TASKS); + for (int k = 0; k < TASKS; ++k) + e.execute(new CheckedRunnable() { + public void realRun() { + done.countDown(); + }}); + assertTrue(done.await(LONG_DELAY_MS, MILLISECONDS)); + } + } + + /** + * allowsCoreThreadTimeOut is by default false. + */ + public void testAllowsCoreThreadTimeOut() { + final ThreadPoolExecutor p = + new ThreadPoolExecutor(2, 2, + 1000, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + assertFalse(p.allowsCoreThreadTimeOut()); + } + } + + /** + * allowCoreThreadTimeOut(true) causes idle threads to time out + */ + public void testAllowCoreThreadTimeOut_true() throws Exception { + long keepAliveTime = timeoutMillis(); + final ThreadPoolExecutor p = + new ThreadPoolExecutor(2, 10, + keepAliveTime, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + p.allowCoreThreadTimeOut(true); + p.execute(new CheckedRunnable() { + public void realRun() { + threadStarted.countDown(); + assertEquals(1, p.getPoolSize()); + }}); + await(threadStarted); + delay(keepAliveTime); + long startTime = System.nanoTime(); + while (p.getPoolSize() > 0 + && millisElapsedSince(startTime) < LONG_DELAY_MS) + Thread.yield(); + assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); + assertEquals(0, p.getPoolSize()); + } + } + + /** + * allowCoreThreadTimeOut(false) causes idle threads not to time out + */ + public void testAllowCoreThreadTimeOut_false() throws Exception { + long keepAliveTime = timeoutMillis(); + final ThreadPoolExecutor p = + new ThreadPoolExecutor(2, 10, + keepAliveTime, MILLISECONDS, + new ArrayBlockingQueue(10)); + try (PoolCleaner cleaner = cleaner(p)) { + final CountDownLatch threadStarted = new CountDownLatch(1); + p.allowCoreThreadTimeOut(false); + p.execute(new CheckedRunnable() { + public void realRun() throws InterruptedException { + threadStarted.countDown(); + assertTrue(p.getPoolSize() >= 1); + }}); + delay(2 * keepAliveTime); + assertTrue(p.getPoolSize() >= 1); + } + } + + /** + * execute allows the same task to be submitted multiple times, even + * if rejected + */ + public void testRejectedRecycledTask() throws InterruptedException { + final int nTasks = 1000; + final CountDownLatch done = new CountDownLatch(nTasks); + final Runnable recycledTask = new Runnable() { + public void run() { + done.countDown(); + }}; + final ThreadPoolExecutor p = + new ThreadPoolExecutor(1, 30, + 60, SECONDS, + new ArrayBlockingQueue(30)); + try (PoolCleaner cleaner = cleaner(p)) { + for (int i = 0; i < nTasks; ++i) { + for (;;) { + try { + p.execute(recycledTask); + break; + } + catch (RejectedExecutionException ignore) {} + } + } + // enough time to run all tasks + assertTrue(done.await(nTasks * SHORT_DELAY_MS, MILLISECONDS)); + } + } + + /** + * get(cancelled task) throws CancellationException + */ + public void testGet_cancelled() throws Exception { + final CountDownLatch done = new CountDownLatch(1); + final ExecutorService e = + new ThreadPoolExecutor(1, 1, + LONG_DELAY_MS, MILLISECONDS, + new LinkedBlockingQueue()); + try (PoolCleaner cleaner = cleaner(e, done)) { + final CountDownLatch blockerStarted = new CountDownLatch(1); + final List> futures = new ArrayList<>(); + for (int i = 0; i < 2; i++) { + Runnable r = new CheckedRunnable() { public void realRun() + throws Throwable { + blockerStarted.countDown(); + assertTrue(done.await(2 * LONG_DELAY_MS, MILLISECONDS)); + }}; + futures.add(e.submit(r)); + } + await(blockerStarted); + for (Future future : futures) future.cancel(false); + for (Future future : futures) { + try { + future.get(); + shouldThrow(); + } catch (CancellationException success) {} + try { + future.get(LONG_DELAY_MS, MILLISECONDS); + shouldThrow(); + } catch (CancellationException success) {} + assertTrue(future.isCancelled()); + assertTrue(future.isDone()); + } + } + } + +} diff --git a/jdk/test/java/util/concurrent/tck/ThreadTest.java b/jdk/test/java/util/concurrent/tck/ThreadTest.java new file mode 100644 index 00000000000..939d590beb9 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/ThreadTest.java @@ -0,0 +1,100 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class ThreadTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + + public static Test suite() { + return new TestSuite(ThreadTest.class); + } + + static class MyHandler implements Thread.UncaughtExceptionHandler { + public void uncaughtException(Thread t, Throwable e) { + e.printStackTrace(); + } + } + + /** + * getUncaughtExceptionHandler returns ThreadGroup unless set, + * otherwise returning value of last setUncaughtExceptionHandler. + */ + public void testGetAndSetUncaughtExceptionHandler() { + // these must be done all at once to avoid state + // dependencies across tests + Thread current = Thread.currentThread(); + ThreadGroup tg = current.getThreadGroup(); + MyHandler eh = new MyHandler(); + assertSame(tg, current.getUncaughtExceptionHandler()); + current.setUncaughtExceptionHandler(eh); + try { + assertSame(eh, current.getUncaughtExceptionHandler()); + } finally { + current.setUncaughtExceptionHandler(null); + } + assertSame(tg, current.getUncaughtExceptionHandler()); + } + + /** + * getDefaultUncaughtExceptionHandler returns value of last + * setDefaultUncaughtExceptionHandler. + */ + public void testGetAndSetDefaultUncaughtExceptionHandler() { + assertEquals(null, Thread.getDefaultUncaughtExceptionHandler()); + // failure due to securityException is OK. + // Would be nice to explicitly test both ways, but cannot yet. + Thread.UncaughtExceptionHandler defaultHandler + = Thread.getDefaultUncaughtExceptionHandler(); + MyHandler eh = new MyHandler(); + try { + Thread.setDefaultUncaughtExceptionHandler(eh); + try { + assertSame(eh, Thread.getDefaultUncaughtExceptionHandler()); + } finally { + Thread.setDefaultUncaughtExceptionHandler(defaultHandler); + } + } catch (SecurityException ok) { + assertNotNull(System.getSecurityManager()); + } + assertSame(defaultHandler, Thread.getDefaultUncaughtExceptionHandler()); + } + + // How to test actually using UEH within junit? + +} diff --git a/jdk/test/java/util/concurrent/tck/TimeUnitTest.java b/jdk/test/java/util/concurrent/tck/TimeUnitTest.java new file mode 100644 index 00000000000..3b33c9a4971 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/TimeUnitTest.java @@ -0,0 +1,463 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import static java.util.concurrent.TimeUnit.DAYS; +import static java.util.concurrent.TimeUnit.HOURS; +import static java.util.concurrent.TimeUnit.MICROSECONDS; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class TimeUnitTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + + public static Test suite() { + return new TestSuite(TimeUnitTest.class); + } + + // (loops to 88888 check increments at all time divisions.) + + /** + * convert correctly converts sample values across the units + */ + public void testConvert() { + for (long t = 0; t < 88888; ++t) { + assertEquals(t*60*60*24, + SECONDS.convert(t, DAYS)); + assertEquals(t*60*60, + SECONDS.convert(t, HOURS)); + assertEquals(t*60, + SECONDS.convert(t, MINUTES)); + assertEquals(t, + SECONDS.convert(t, SECONDS)); + assertEquals(t, + SECONDS.convert(1000L*t, MILLISECONDS)); + assertEquals(t, + SECONDS.convert(1000000L*t, MICROSECONDS)); + assertEquals(t, + SECONDS.convert(1000000000L*t, NANOSECONDS)); + + assertEquals(1000L*t*60*60*24, + MILLISECONDS.convert(t, DAYS)); + assertEquals(1000L*t*60*60, + MILLISECONDS.convert(t, HOURS)); + assertEquals(1000L*t*60, + MILLISECONDS.convert(t, MINUTES)); + assertEquals(1000L*t, + MILLISECONDS.convert(t, SECONDS)); + assertEquals(t, + MILLISECONDS.convert(t, MILLISECONDS)); + assertEquals(t, + MILLISECONDS.convert(1000L*t, MICROSECONDS)); + assertEquals(t, + MILLISECONDS.convert(1000000L*t, NANOSECONDS)); + + assertEquals(1000000L*t*60*60*24, + MICROSECONDS.convert(t, DAYS)); + assertEquals(1000000L*t*60*60, + MICROSECONDS.convert(t, HOURS)); + assertEquals(1000000L*t*60, + MICROSECONDS.convert(t, MINUTES)); + assertEquals(1000000L*t, + MICROSECONDS.convert(t, SECONDS)); + assertEquals(1000L*t, + MICROSECONDS.convert(t, MILLISECONDS)); + assertEquals(t, + MICROSECONDS.convert(t, MICROSECONDS)); + assertEquals(t, + MICROSECONDS.convert(1000L*t, NANOSECONDS)); + + assertEquals(1000000000L*t*60*60*24, + NANOSECONDS.convert(t, DAYS)); + assertEquals(1000000000L*t*60*60, + NANOSECONDS.convert(t, HOURS)); + assertEquals(1000000000L*t*60, + NANOSECONDS.convert(t, MINUTES)); + assertEquals(1000000000L*t, + NANOSECONDS.convert(t, SECONDS)); + assertEquals(1000000L*t, + NANOSECONDS.convert(t, MILLISECONDS)); + assertEquals(1000L*t, + NANOSECONDS.convert(t, MICROSECONDS)); + assertEquals(t, + NANOSECONDS.convert(t, NANOSECONDS)); + } + } + + /** + * toNanos correctly converts sample values in different units to + * nanoseconds + */ + public void testToNanos() { + for (long t = 0; t < 88888; ++t) { + assertEquals(t*1000000000L*60*60*24, + DAYS.toNanos(t)); + assertEquals(t*1000000000L*60*60, + HOURS.toNanos(t)); + assertEquals(t*1000000000L*60, + MINUTES.toNanos(t)); + assertEquals(1000000000L*t, + SECONDS.toNanos(t)); + assertEquals(1000000L*t, + MILLISECONDS.toNanos(t)); + assertEquals(1000L*t, + MICROSECONDS.toNanos(t)); + assertEquals(t, + NANOSECONDS.toNanos(t)); + } + } + + /** + * toMicros correctly converts sample values in different units to + * microseconds + */ + public void testToMicros() { + for (long t = 0; t < 88888; ++t) { + assertEquals(t*1000000L*60*60*24, + DAYS.toMicros(t)); + assertEquals(t*1000000L*60*60, + HOURS.toMicros(t)); + assertEquals(t*1000000L*60, + MINUTES.toMicros(t)); + assertEquals(1000000L*t, + SECONDS.toMicros(t)); + assertEquals(1000L*t, + MILLISECONDS.toMicros(t)); + assertEquals(t, + MICROSECONDS.toMicros(t)); + assertEquals(t, + NANOSECONDS.toMicros(t*1000L)); + } + } + + /** + * toMillis correctly converts sample values in different units to + * milliseconds + */ + public void testToMillis() { + for (long t = 0; t < 88888; ++t) { + assertEquals(t*1000L*60*60*24, + DAYS.toMillis(t)); + assertEquals(t*1000L*60*60, + HOURS.toMillis(t)); + assertEquals(t*1000L*60, + MINUTES.toMillis(t)); + assertEquals(1000L*t, + SECONDS.toMillis(t)); + assertEquals(t, + MILLISECONDS.toMillis(t)); + assertEquals(t, + MICROSECONDS.toMillis(t*1000L)); + assertEquals(t, + NANOSECONDS.toMillis(t*1000000L)); + } + } + + /** + * toSeconds correctly converts sample values in different units to + * seconds + */ + public void testToSeconds() { + for (long t = 0; t < 88888; ++t) { + assertEquals(t*60*60*24, + DAYS.toSeconds(t)); + assertEquals(t*60*60, + HOURS.toSeconds(t)); + assertEquals(t*60, + MINUTES.toSeconds(t)); + assertEquals(t, + SECONDS.toSeconds(t)); + assertEquals(t, + MILLISECONDS.toSeconds(t*1000L)); + assertEquals(t, + MICROSECONDS.toSeconds(t*1000000L)); + assertEquals(t, + NANOSECONDS.toSeconds(t*1000000000L)); + } + } + + /** + * toMinutes correctly converts sample values in different units to + * minutes + */ + public void testToMinutes() { + for (long t = 0; t < 88888; ++t) { + assertEquals(t*60*24, + DAYS.toMinutes(t)); + assertEquals(t*60, + HOURS.toMinutes(t)); + assertEquals(t, + MINUTES.toMinutes(t)); + assertEquals(t, + SECONDS.toMinutes(t*60)); + assertEquals(t, + MILLISECONDS.toMinutes(t*1000L*60)); + assertEquals(t, + MICROSECONDS.toMinutes(t*1000000L*60)); + assertEquals(t, + NANOSECONDS.toMinutes(t*1000000000L*60)); + } + } + + /** + * toHours correctly converts sample values in different units to + * hours + */ + public void testToHours() { + for (long t = 0; t < 88888; ++t) { + assertEquals(t*24, + DAYS.toHours(t)); + assertEquals(t, + HOURS.toHours(t)); + assertEquals(t, + MINUTES.toHours(t*60)); + assertEquals(t, + SECONDS.toHours(t*60*60)); + assertEquals(t, + MILLISECONDS.toHours(t*1000L*60*60)); + assertEquals(t, + MICROSECONDS.toHours(t*1000000L*60*60)); + assertEquals(t, + NANOSECONDS.toHours(t*1000000000L*60*60)); + } + } + + /** + * toDays correctly converts sample values in different units to + * days + */ + public void testToDays() { + for (long t = 0; t < 88888; ++t) { + assertEquals(t, + DAYS.toDays(t)); + assertEquals(t, + HOURS.toDays(t*24)); + assertEquals(t, + MINUTES.toDays(t*60*24)); + assertEquals(t, + SECONDS.toDays(t*60*60*24)); + assertEquals(t, + MILLISECONDS.toDays(t*1000L*60*60*24)); + assertEquals(t, + MICROSECONDS.toDays(t*1000000L*60*60*24)); + assertEquals(t, + NANOSECONDS.toDays(t*1000000000L*60*60*24)); + } + } + + /** + * convert saturates positive too-large values to Long.MAX_VALUE + * and negative to LONG.MIN_VALUE + */ + public void testConvertSaturate() { + assertEquals(Long.MAX_VALUE, + NANOSECONDS.convert(Long.MAX_VALUE / 2, SECONDS)); + assertEquals(Long.MIN_VALUE, + NANOSECONDS.convert(-Long.MAX_VALUE / 4, SECONDS)); + assertEquals(Long.MAX_VALUE, + NANOSECONDS.convert(Long.MAX_VALUE / 2, MINUTES)); + assertEquals(Long.MIN_VALUE, + NANOSECONDS.convert(-Long.MAX_VALUE / 4, MINUTES)); + assertEquals(Long.MAX_VALUE, + NANOSECONDS.convert(Long.MAX_VALUE / 2, HOURS)); + assertEquals(Long.MIN_VALUE, + NANOSECONDS.convert(-Long.MAX_VALUE / 4, HOURS)); + assertEquals(Long.MAX_VALUE, + NANOSECONDS.convert(Long.MAX_VALUE / 2, DAYS)); + assertEquals(Long.MIN_VALUE, + NANOSECONDS.convert(-Long.MAX_VALUE / 4, DAYS)); + } + + /** + * toNanos saturates positive too-large values to Long.MAX_VALUE + * and negative to LONG.MIN_VALUE + */ + public void testToNanosSaturate() { + assertEquals(Long.MAX_VALUE, + MILLISECONDS.toNanos(Long.MAX_VALUE / 2)); + assertEquals(Long.MIN_VALUE, + MILLISECONDS.toNanos(-Long.MAX_VALUE / 3)); + } + + /** + * toString returns name of unit + */ + public void testToString() { + assertEquals("SECONDS", SECONDS.toString()); + } + + /** + * name returns name of unit + */ + public void testName() { + assertEquals("SECONDS", SECONDS.name()); + } + + /** + * Timed wait without holding lock throws + * IllegalMonitorStateException + */ + public void testTimedWait_IllegalMonitorException() { + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + Object o = new Object(); + TimeUnit tu = MILLISECONDS; + + try { + tu.timedWait(o, LONG_DELAY_MS); + threadShouldThrow(); + } catch (IllegalMonitorStateException success) {} + }}); + + awaitTermination(t); + } + + /** + * timedWait throws InterruptedException when interrupted + */ + public void testTimedWait_Interruptible() { + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + Object o = new Object(); + TimeUnit tu = MILLISECONDS; + + Thread.currentThread().interrupt(); + try { + synchronized (o) { + tu.timedWait(o, LONG_DELAY_MS); + } + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + synchronized (o) { + tu.timedWait(o, LONG_DELAY_MS); + } + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * timedJoin throws InterruptedException when interrupted + */ + public void testTimedJoin_Interruptible() { + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + final Thread s = newStartedThread(new CheckedInterruptedRunnable() { + public void realRun() throws InterruptedException { + Thread.sleep(LONG_DELAY_MS); + }}); + final Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + TimeUnit tu = MILLISECONDS; + Thread.currentThread().interrupt(); + try { + tu.timedJoin(s, LONG_DELAY_MS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + tu.timedJoin(s, LONG_DELAY_MS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + s.interrupt(); + awaitTermination(s); + } + + /** + * timedSleep throws InterruptedException when interrupted + */ + public void testTimedSleep_Interruptible() { + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); + Thread t = newStartedThread(new CheckedRunnable() { + public void realRun() throws InterruptedException { + TimeUnit tu = MILLISECONDS; + Thread.currentThread().interrupt(); + try { + tu.sleep(LONG_DELAY_MS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + + pleaseInterrupt.countDown(); + try { + tu.sleep(LONG_DELAY_MS); + shouldThrow(); + } catch (InterruptedException success) {} + assertFalse(Thread.interrupted()); + }}); + + await(pleaseInterrupt); + assertThreadStaysAlive(t); + t.interrupt(); + awaitTermination(t); + } + + /** + * a deserialized serialized unit is the same instance + */ + public void testSerialization() throws Exception { + TimeUnit x = MILLISECONDS; + assertSame(x, serialClone(x)); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/TreeMapTest.java b/jdk/test/java/util/concurrent/tck/TreeMapTest.java new file mode 100644 index 00000000000..ebf1dd01b9b --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/TreeMapTest.java @@ -0,0 +1,1111 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.NavigableMap; +import java.util.NavigableSet; +import java.util.NoSuchElementException; +import java.util.Random; +import java.util.Set; +import java.util.TreeMap; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class TreeMapTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(TreeMapTest.class); + } + + /** + * Returns a new map from Integers 1-5 to Strings "A"-"E". + */ + private static TreeMap map5() { + TreeMap map = new TreeMap(); + assertTrue(map.isEmpty()); + map.put(one, "A"); + map.put(five, "E"); + map.put(three, "C"); + map.put(two, "B"); + map.put(four, "D"); + assertFalse(map.isEmpty()); + assertEquals(5, map.size()); + return map; + } + + /** + * clear removes all pairs + */ + public void testClear() { + TreeMap map = map5(); + map.clear(); + assertEquals(0, map.size()); + } + + /** + * copy constructor creates map equal to source map + */ + public void testConstructFromSorted() { + TreeMap map = map5(); + TreeMap map2 = new TreeMap(map); + assertEquals(map, map2); + } + + /** + * Maps with same contents are equal + */ + public void testEquals() { + TreeMap map1 = map5(); + TreeMap map2 = map5(); + assertEquals(map1, map2); + assertEquals(map2, map1); + map1.clear(); + assertFalse(map1.equals(map2)); + assertFalse(map2.equals(map1)); + } + + /** + * containsKey returns true for contained key + */ + public void testContainsKey() { + TreeMap map = map5(); + assertTrue(map.containsKey(one)); + assertFalse(map.containsKey(zero)); + } + + /** + * containsValue returns true for held values + */ + public void testContainsValue() { + TreeMap map = map5(); + assertTrue(map.containsValue("A")); + assertFalse(map.containsValue("Z")); + } + + /** + * get returns the correct element at the given key, + * or null if not present + */ + public void testGet() { + TreeMap map = map5(); + assertEquals("A", (String)map.get(one)); + TreeMap empty = new TreeMap(); + assertNull(empty.get(one)); + } + + /** + * isEmpty is true of empty map and false for non-empty + */ + public void testIsEmpty() { + TreeMap empty = new TreeMap(); + TreeMap map = map5(); + assertTrue(empty.isEmpty()); + assertFalse(map.isEmpty()); + } + + /** + * firstKey returns first key + */ + public void testFirstKey() { + TreeMap map = map5(); + assertEquals(one, map.firstKey()); + } + + /** + * lastKey returns last key + */ + public void testLastKey() { + TreeMap map = map5(); + assertEquals(five, map.lastKey()); + } + + /** + * keySet.toArray returns contains all keys + */ + public void testKeySetToArray() { + TreeMap map = map5(); + Set s = map.keySet(); + Object[] ar = s.toArray(); + assertTrue(s.containsAll(Arrays.asList(ar))); + assertEquals(5, ar.length); + ar[0] = m10; + assertFalse(s.containsAll(Arrays.asList(ar))); + } + + /** + * descendingkeySet.toArray returns contains all keys + */ + public void testDescendingKeySetToArray() { + TreeMap map = map5(); + Set s = map.descendingKeySet(); + Object[] ar = s.toArray(); + assertEquals(5, ar.length); + assertTrue(s.containsAll(Arrays.asList(ar))); + ar[0] = m10; + assertFalse(s.containsAll(Arrays.asList(ar))); + } + + /** + * keySet returns a Set containing all the keys + */ + public void testKeySet() { + TreeMap map = map5(); + Set s = map.keySet(); + assertEquals(5, s.size()); + assertTrue(s.contains(one)); + assertTrue(s.contains(two)); + assertTrue(s.contains(three)); + assertTrue(s.contains(four)); + assertTrue(s.contains(five)); + } + + /** + * keySet is ordered + */ + public void testKeySetOrder() { + TreeMap map = map5(); + Set s = map.keySet(); + Iterator i = s.iterator(); + Integer last = (Integer)i.next(); + assertEquals(last, one); + int count = 1; + while (i.hasNext()) { + Integer k = (Integer)i.next(); + assertTrue(last.compareTo(k) < 0); + last = k; + ++count; + } + assertEquals(5, count); + } + + /** + * descending iterator of key set is inverse ordered + */ + public void testKeySetDescendingIteratorOrder() { + TreeMap map = map5(); + NavigableSet s = map.navigableKeySet(); + Iterator i = s.descendingIterator(); + Integer last = (Integer)i.next(); + assertEquals(last, five); + int count = 1; + while (i.hasNext()) { + Integer k = (Integer)i.next(); + assertTrue(last.compareTo(k) > 0); + last = k; + ++count; + } + assertEquals(5, count); + } + + /** + * descendingKeySet is ordered + */ + public void testDescendingKeySetOrder() { + TreeMap map = map5(); + Set s = map.descendingKeySet(); + Iterator i = s.iterator(); + Integer last = (Integer)i.next(); + assertEquals(last, five); + int count = 1; + while (i.hasNext()) { + Integer k = (Integer)i.next(); + assertTrue(last.compareTo(k) > 0); + last = k; + ++count; + } + assertEquals(5, count); + } + + /** + * descending iterator of descendingKeySet is ordered + */ + public void testDescendingKeySetDescendingIteratorOrder() { + TreeMap map = map5(); + NavigableSet s = map.descendingKeySet(); + Iterator i = s.descendingIterator(); + Integer last = (Integer)i.next(); + assertEquals(last, one); + int count = 1; + while (i.hasNext()) { + Integer k = (Integer)i.next(); + assertTrue(last.compareTo(k) < 0); + last = k; + ++count; + } + assertEquals(5, count); + } + + /** + * values collection contains all values + */ + public void testValues() { + TreeMap map = map5(); + Collection s = map.values(); + assertEquals(5, s.size()); + assertTrue(s.contains("A")); + assertTrue(s.contains("B")); + assertTrue(s.contains("C")); + assertTrue(s.contains("D")); + assertTrue(s.contains("E")); + } + + /** + * entrySet contains all pairs + */ + public void testEntrySet() { + TreeMap map = map5(); + Set s = map.entrySet(); + assertEquals(5, s.size()); + Iterator it = s.iterator(); + while (it.hasNext()) { + Map.Entry e = (Map.Entry) it.next(); + assertTrue( + (e.getKey().equals(one) && e.getValue().equals("A")) || + (e.getKey().equals(two) && e.getValue().equals("B")) || + (e.getKey().equals(three) && e.getValue().equals("C")) || + (e.getKey().equals(four) && e.getValue().equals("D")) || + (e.getKey().equals(five) && e.getValue().equals("E"))); + } + } + + /** + * descendingEntrySet contains all pairs + */ + public void testDescendingEntrySet() { + TreeMap map = map5(); + Set s = map.descendingMap().entrySet(); + assertEquals(5, s.size()); + Iterator it = s.iterator(); + while (it.hasNext()) { + Map.Entry e = (Map.Entry) it.next(); + assertTrue( + (e.getKey().equals(one) && e.getValue().equals("A")) || + (e.getKey().equals(two) && e.getValue().equals("B")) || + (e.getKey().equals(three) && e.getValue().equals("C")) || + (e.getKey().equals(four) && e.getValue().equals("D")) || + (e.getKey().equals(five) && e.getValue().equals("E"))); + } + } + + /** + * entrySet.toArray contains all entries + */ + public void testEntrySetToArray() { + TreeMap map = map5(); + Set s = map.entrySet(); + Object[] ar = s.toArray(); + assertEquals(5, ar.length); + for (int i = 0; i < 5; ++i) { + assertTrue(map.containsKey(((Map.Entry)(ar[i])).getKey())); + assertTrue(map.containsValue(((Map.Entry)(ar[i])).getValue())); + } + } + + /** + * descendingEntrySet.toArray contains all entries + */ + public void testDescendingEntrySetToArray() { + TreeMap map = map5(); + Set s = map.descendingMap().entrySet(); + Object[] ar = s.toArray(); + assertEquals(5, ar.length); + for (int i = 0; i < 5; ++i) { + assertTrue(map.containsKey(((Map.Entry)(ar[i])).getKey())); + assertTrue(map.containsValue(((Map.Entry)(ar[i])).getValue())); + } + } + + /** + * putAll adds all key-value pairs from the given map + */ + public void testPutAll() { + TreeMap empty = new TreeMap(); + TreeMap map = map5(); + empty.putAll(map); + assertEquals(5, empty.size()); + assertTrue(empty.containsKey(one)); + assertTrue(empty.containsKey(two)); + assertTrue(empty.containsKey(three)); + assertTrue(empty.containsKey(four)); + assertTrue(empty.containsKey(five)); + } + + /** + * remove removes the correct key-value pair from the map + */ + public void testRemove() { + TreeMap map = map5(); + map.remove(five); + assertEquals(4, map.size()); + assertFalse(map.containsKey(five)); + } + + /** + * lowerEntry returns preceding entry. + */ + public void testLowerEntry() { + TreeMap map = map5(); + Map.Entry e1 = map.lowerEntry(three); + assertEquals(two, e1.getKey()); + + Map.Entry e2 = map.lowerEntry(six); + assertEquals(five, e2.getKey()); + + Map.Entry e3 = map.lowerEntry(one); + assertNull(e3); + + Map.Entry e4 = map.lowerEntry(zero); + assertNull(e4); + } + + /** + * higherEntry returns next entry. + */ + public void testHigherEntry() { + TreeMap map = map5(); + Map.Entry e1 = map.higherEntry(three); + assertEquals(four, e1.getKey()); + + Map.Entry e2 = map.higherEntry(zero); + assertEquals(one, e2.getKey()); + + Map.Entry e3 = map.higherEntry(five); + assertNull(e3); + + Map.Entry e4 = map.higherEntry(six); + assertNull(e4); + } + + /** + * floorEntry returns preceding entry. + */ + public void testFloorEntry() { + TreeMap map = map5(); + Map.Entry e1 = map.floorEntry(three); + assertEquals(three, e1.getKey()); + + Map.Entry e2 = map.floorEntry(six); + assertEquals(five, e2.getKey()); + + Map.Entry e3 = map.floorEntry(one); + assertEquals(one, e3.getKey()); + + Map.Entry e4 = map.floorEntry(zero); + assertNull(e4); + } + + /** + * ceilingEntry returns next entry. + */ + public void testCeilingEntry() { + TreeMap map = map5(); + Map.Entry e1 = map.ceilingEntry(three); + assertEquals(three, e1.getKey()); + + Map.Entry e2 = map.ceilingEntry(zero); + assertEquals(one, e2.getKey()); + + Map.Entry e3 = map.ceilingEntry(five); + assertEquals(five, e3.getKey()); + + Map.Entry e4 = map.ceilingEntry(six); + assertNull(e4); + } + + /** + * lowerKey returns preceding element + */ + public void testLowerKey() { + TreeMap q = map5(); + Object e1 = q.lowerKey(three); + assertEquals(two, e1); + + Object e2 = q.lowerKey(six); + assertEquals(five, e2); + + Object e3 = q.lowerKey(one); + assertNull(e3); + + Object e4 = q.lowerKey(zero); + assertNull(e4); + } + + /** + * higherKey returns next element + */ + public void testHigherKey() { + TreeMap q = map5(); + Object e1 = q.higherKey(three); + assertEquals(four, e1); + + Object e2 = q.higherKey(zero); + assertEquals(one, e2); + + Object e3 = q.higherKey(five); + assertNull(e3); + + Object e4 = q.higherKey(six); + assertNull(e4); + } + + /** + * floorKey returns preceding element + */ + public void testFloorKey() { + TreeMap q = map5(); + Object e1 = q.floorKey(three); + assertEquals(three, e1); + + Object e2 = q.floorKey(six); + assertEquals(five, e2); + + Object e3 = q.floorKey(one); + assertEquals(one, e3); + + Object e4 = q.floorKey(zero); + assertNull(e4); + } + + /** + * ceilingKey returns next element + */ + public void testCeilingKey() { + TreeMap q = map5(); + Object e1 = q.ceilingKey(three); + assertEquals(three, e1); + + Object e2 = q.ceilingKey(zero); + assertEquals(one, e2); + + Object e3 = q.ceilingKey(five); + assertEquals(five, e3); + + Object e4 = q.ceilingKey(six); + assertNull(e4); + } + + /** + * pollFirstEntry returns entries in order + */ + public void testPollFirstEntry() { + TreeMap map = map5(); + Map.Entry e = map.pollFirstEntry(); + assertEquals(one, e.getKey()); + assertEquals("A", e.getValue()); + e = map.pollFirstEntry(); + assertEquals(two, e.getKey()); + map.put(one, "A"); + e = map.pollFirstEntry(); + assertEquals(one, e.getKey()); + assertEquals("A", e.getValue()); + e = map.pollFirstEntry(); + assertEquals(three, e.getKey()); + map.remove(four); + e = map.pollFirstEntry(); + assertEquals(five, e.getKey()); + try { + e.setValue("A"); + shouldThrow(); + } catch (UnsupportedOperationException success) {} + e = map.pollFirstEntry(); + assertNull(e); + } + + /** + * pollLastEntry returns entries in order + */ + public void testPollLastEntry() { + TreeMap map = map5(); + Map.Entry e = map.pollLastEntry(); + assertEquals(five, e.getKey()); + assertEquals("E", e.getValue()); + e = map.pollLastEntry(); + assertEquals(four, e.getKey()); + map.put(five, "E"); + e = map.pollLastEntry(); + assertEquals(five, e.getKey()); + assertEquals("E", e.getValue()); + e = map.pollLastEntry(); + assertEquals(three, e.getKey()); + map.remove(two); + e = map.pollLastEntry(); + assertEquals(one, e.getKey()); + try { + e.setValue("E"); + shouldThrow(); + } catch (UnsupportedOperationException success) {} + e = map.pollLastEntry(); + assertNull(e); + } + + /** + * size returns the correct values + */ + public void testSize() { + TreeMap map = map5(); + TreeMap empty = new TreeMap(); + assertEquals(0, empty.size()); + assertEquals(5, map.size()); + } + + /** + * toString contains toString of elements + */ + public void testToString() { + TreeMap map = map5(); + String s = map.toString(); + for (int i = 1; i <= 5; ++i) { + assertTrue(s.contains(String.valueOf(i))); + } + } + + // Exception tests + + /** + * get(null) of nonempty map throws NPE + */ + public void testGet_NullPointerException() { + TreeMap c = map5(); + try { + c.get(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * containsKey(null) of nonempty map throws NPE + */ + public void testContainsKey_NullPointerException() { + TreeMap c = map5(); + try { + c.containsKey(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * remove(null) throws NPE for nonempty map + */ + public void testRemove1_NullPointerException() { + TreeMap c = new TreeMap(); + c.put("sadsdf", "asdads"); + try { + c.remove(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * A deserialized map equals original + */ + public void testSerialization() throws Exception { + NavigableMap x = map5(); + NavigableMap y = serialClone(x); + + assertNotSame(x, y); + assertEquals(x.size(), y.size()); + assertEquals(x.toString(), y.toString()); + assertEquals(x, y); + assertEquals(y, x); + } + + /** + * subMap returns map with keys in requested range + */ + public void testSubMapContents() { + TreeMap map = map5(); + NavigableMap sm = map.subMap(two, true, four, false); + assertEquals(two, sm.firstKey()); + assertEquals(three, sm.lastKey()); + assertEquals(2, sm.size()); + assertFalse(sm.containsKey(one)); + assertTrue(sm.containsKey(two)); + assertTrue(sm.containsKey(three)); + assertFalse(sm.containsKey(four)); + assertFalse(sm.containsKey(five)); + Iterator i = sm.keySet().iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(two, k); + k = (Integer)(i.next()); + assertEquals(three, k); + assertFalse(i.hasNext()); + Iterator r = sm.descendingKeySet().iterator(); + k = (Integer)(r.next()); + assertEquals(three, k); + k = (Integer)(r.next()); + assertEquals(two, k); + assertFalse(r.hasNext()); + + Iterator j = sm.keySet().iterator(); + j.next(); + j.remove(); + assertFalse(map.containsKey(two)); + assertEquals(4, map.size()); + assertEquals(1, sm.size()); + assertEquals(three, sm.firstKey()); + assertEquals(three, sm.lastKey()); + assertEquals("C", sm.remove(three)); + assertTrue(sm.isEmpty()); + assertEquals(3, map.size()); + } + + public void testSubMapContents2() { + TreeMap map = map5(); + NavigableMap sm = map.subMap(two, true, three, false); + assertEquals(1, sm.size()); + assertEquals(two, sm.firstKey()); + assertEquals(two, sm.lastKey()); + assertFalse(sm.containsKey(one)); + assertTrue(sm.containsKey(two)); + assertFalse(sm.containsKey(three)); + assertFalse(sm.containsKey(four)); + assertFalse(sm.containsKey(five)); + Iterator i = sm.keySet().iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(two, k); + assertFalse(i.hasNext()); + Iterator r = sm.descendingKeySet().iterator(); + k = (Integer)(r.next()); + assertEquals(two, k); + assertFalse(r.hasNext()); + + Iterator j = sm.keySet().iterator(); + j.next(); + j.remove(); + assertFalse(map.containsKey(two)); + assertEquals(4, map.size()); + assertEquals(0, sm.size()); + assertTrue(sm.isEmpty()); + assertSame(sm.remove(three), null); + assertEquals(4, map.size()); + } + + /** + * headMap returns map with keys in requested range + */ + public void testHeadMapContents() { + TreeMap map = map5(); + NavigableMap sm = map.headMap(four, false); + assertTrue(sm.containsKey(one)); + assertTrue(sm.containsKey(two)); + assertTrue(sm.containsKey(three)); + assertFalse(sm.containsKey(four)); + assertFalse(sm.containsKey(five)); + Iterator i = sm.keySet().iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(one, k); + k = (Integer)(i.next()); + assertEquals(two, k); + k = (Integer)(i.next()); + assertEquals(three, k); + assertFalse(i.hasNext()); + sm.clear(); + assertTrue(sm.isEmpty()); + assertEquals(2, map.size()); + assertEquals(four, map.firstKey()); + } + + /** + * headMap returns map with keys in requested range + */ + public void testTailMapContents() { + TreeMap map = map5(); + NavigableMap sm = map.tailMap(two, true); + assertFalse(sm.containsKey(one)); + assertTrue(sm.containsKey(two)); + assertTrue(sm.containsKey(three)); + assertTrue(sm.containsKey(four)); + assertTrue(sm.containsKey(five)); + Iterator i = sm.keySet().iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(two, k); + k = (Integer)(i.next()); + assertEquals(three, k); + k = (Integer)(i.next()); + assertEquals(four, k); + k = (Integer)(i.next()); + assertEquals(five, k); + assertFalse(i.hasNext()); + Iterator r = sm.descendingKeySet().iterator(); + k = (Integer)(r.next()); + assertEquals(five, k); + k = (Integer)(r.next()); + assertEquals(four, k); + k = (Integer)(r.next()); + assertEquals(three, k); + k = (Integer)(r.next()); + assertEquals(two, k); + assertFalse(r.hasNext()); + + Iterator ei = sm.entrySet().iterator(); + Map.Entry e; + e = (Map.Entry)(ei.next()); + assertEquals(two, e.getKey()); + assertEquals("B", e.getValue()); + e = (Map.Entry)(ei.next()); + assertEquals(three, e.getKey()); + assertEquals("C", e.getValue()); + e = (Map.Entry)(ei.next()); + assertEquals(four, e.getKey()); + assertEquals("D", e.getValue()); + e = (Map.Entry)(ei.next()); + assertEquals(five, e.getKey()); + assertEquals("E", e.getValue()); + assertFalse(i.hasNext()); + + NavigableMap ssm = sm.tailMap(four, true); + assertEquals(four, ssm.firstKey()); + assertEquals(five, ssm.lastKey()); + assertEquals("D", ssm.remove(four)); + assertEquals(1, ssm.size()); + assertEquals(3, sm.size()); + assertEquals(4, map.size()); + } + + Random rnd = new Random(666); + BitSet bs; + + /** + * Submaps of submaps subdivide correctly + */ + public void testRecursiveSubMaps() throws Exception { + int mapSize = expensiveTests ? 1000 : 100; + Class cl = TreeMap.class; + NavigableMap map = newMap(cl); + bs = new BitSet(mapSize); + + populate(map, mapSize); + check(map, 0, mapSize - 1, true); + check(map.descendingMap(), 0, mapSize - 1, false); + + mutateMap(map, 0, mapSize - 1); + check(map, 0, mapSize - 1, true); + check(map.descendingMap(), 0, mapSize - 1, false); + + bashSubMap(map.subMap(0, true, mapSize, false), + 0, mapSize - 1, true); + } + + static NavigableMap newMap(Class cl) throws Exception { + NavigableMap result + = (NavigableMap) cl.newInstance(); + assertEquals(0, result.size()); + assertFalse(result.keySet().iterator().hasNext()); + return result; + } + + void populate(NavigableMap map, int limit) { + for (int i = 0, n = 2 * limit / 3; i < n; i++) { + int key = rnd.nextInt(limit); + put(map, key); + } + } + + void mutateMap(NavigableMap map, int min, int max) { + int size = map.size(); + int rangeSize = max - min + 1; + + // Remove a bunch of entries directly + for (int i = 0, n = rangeSize / 2; i < n; i++) { + remove(map, min - 5 + rnd.nextInt(rangeSize + 10)); + } + + // Remove a bunch of entries with iterator + for (Iterator it = map.keySet().iterator(); it.hasNext(); ) { + if (rnd.nextBoolean()) { + bs.clear(it.next()); + it.remove(); + } + } + + // Add entries till we're back to original size + while (map.size() < size) { + int key = min + rnd.nextInt(rangeSize); + assertTrue(key >= min && key <= max); + put(map, key); + } + } + + void mutateSubMap(NavigableMap map, int min, int max) { + int size = map.size(); + int rangeSize = max - min + 1; + + // Remove a bunch of entries directly + for (int i = 0, n = rangeSize / 2; i < n; i++) { + remove(map, min - 5 + rnd.nextInt(rangeSize + 10)); + } + + // Remove a bunch of entries with iterator + for (Iterator it = map.keySet().iterator(); it.hasNext(); ) { + if (rnd.nextBoolean()) { + bs.clear(it.next()); + it.remove(); + } + } + + // Add entries till we're back to original size + while (map.size() < size) { + int key = min - 5 + rnd.nextInt(rangeSize + 10); + if (key >= min && key <= max) { + put(map, key); + } else { + try { + map.put(key, 2 * key); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + } + + void put(NavigableMap map, int key) { + if (map.put(key, 2 * key) == null) + bs.set(key); + } + + void remove(NavigableMap map, int key) { + if (map.remove(key) != null) + bs.clear(key); + } + + void bashSubMap(NavigableMap map, + int min, int max, boolean ascending) { + check(map, min, max, ascending); + check(map.descendingMap(), min, max, !ascending); + + mutateSubMap(map, min, max); + check(map, min, max, ascending); + check(map.descendingMap(), min, max, !ascending); + + // Recurse + if (max - min < 2) + return; + int midPoint = (min + max) / 2; + + // headMap - pick direction and endpoint inclusion randomly + boolean incl = rnd.nextBoolean(); + NavigableMap hm = map.headMap(midPoint, incl); + if (ascending) { + if (rnd.nextBoolean()) + bashSubMap(hm, min, midPoint - (incl ? 0 : 1), true); + else + bashSubMap(hm.descendingMap(), min, midPoint - (incl ? 0 : 1), + false); + } else { + if (rnd.nextBoolean()) + bashSubMap(hm, midPoint + (incl ? 0 : 1), max, false); + else + bashSubMap(hm.descendingMap(), midPoint + (incl ? 0 : 1), max, + true); + } + + // tailMap - pick direction and endpoint inclusion randomly + incl = rnd.nextBoolean(); + NavigableMap tm = map.tailMap(midPoint,incl); + if (ascending) { + if (rnd.nextBoolean()) + bashSubMap(tm, midPoint + (incl ? 0 : 1), max, true); + else + bashSubMap(tm.descendingMap(), midPoint + (incl ? 0 : 1), max, + false); + } else { + if (rnd.nextBoolean()) { + bashSubMap(tm, min, midPoint - (incl ? 0 : 1), false); + } else { + bashSubMap(tm.descendingMap(), min, midPoint - (incl ? 0 : 1), + true); + } + } + + // subMap - pick direction and endpoint inclusion randomly + int rangeSize = max - min + 1; + int[] endpoints = new int[2]; + endpoints[0] = min + rnd.nextInt(rangeSize); + endpoints[1] = min + rnd.nextInt(rangeSize); + Arrays.sort(endpoints); + boolean lowIncl = rnd.nextBoolean(); + boolean highIncl = rnd.nextBoolean(); + if (ascending) { + NavigableMap sm = map.subMap( + endpoints[0], lowIncl, endpoints[1], highIncl); + if (rnd.nextBoolean()) + bashSubMap(sm, endpoints[0] + (lowIncl ? 0 : 1), + endpoints[1] - (highIncl ? 0 : 1), true); + else + bashSubMap(sm.descendingMap(), endpoints[0] + (lowIncl ? 0 : 1), + endpoints[1] - (highIncl ? 0 : 1), false); + } else { + NavigableMap sm = map.subMap( + endpoints[1], highIncl, endpoints[0], lowIncl); + if (rnd.nextBoolean()) + bashSubMap(sm, endpoints[0] + (lowIncl ? 0 : 1), + endpoints[1] - (highIncl ? 0 : 1), false); + else + bashSubMap(sm.descendingMap(), endpoints[0] + (lowIncl ? 0 : 1), + endpoints[1] - (highIncl ? 0 : 1), true); + } + } + + /** + * min and max are both inclusive. If max < min, interval is empty. + */ + void check(NavigableMap map, + final int min, final int max, final boolean ascending) { + class ReferenceSet { + int lower(int key) { + return ascending ? lowerAscending(key) : higherAscending(key); + } + int floor(int key) { + return ascending ? floorAscending(key) : ceilingAscending(key); + } + int ceiling(int key) { + return ascending ? ceilingAscending(key) : floorAscending(key); + } + int higher(int key) { + return ascending ? higherAscending(key) : lowerAscending(key); + } + int first() { + return ascending ? firstAscending() : lastAscending(); + } + int last() { + return ascending ? lastAscending() : firstAscending(); + } + int lowerAscending(int key) { + return floorAscending(key - 1); + } + int floorAscending(int key) { + if (key < min) + return -1; + else if (key > max) + key = max; + + // BitSet should support this! Test would run much faster + while (key >= min) { + if (bs.get(key)) + return key; + key--; + } + return -1; + } + int ceilingAscending(int key) { + if (key < min) + key = min; + else if (key > max) + return -1; + int result = bs.nextSetBit(key); + return result > max ? -1 : result; + } + int higherAscending(int key) { + return ceilingAscending(key + 1); + } + private int firstAscending() { + int result = ceilingAscending(min); + return result > max ? -1 : result; + } + private int lastAscending() { + int result = floorAscending(max); + return result < min ? -1 : result; + } + } + ReferenceSet rs = new ReferenceSet(); + + // Test contents using containsKey + int size = 0; + for (int i = min; i <= max; i++) { + boolean bsContainsI = bs.get(i); + assertEquals(bsContainsI, map.containsKey(i)); + if (bsContainsI) + size++; + } + assertEquals(size, map.size()); + + // Test contents using contains keySet iterator + int size2 = 0; + int previousKey = -1; + for (int key : map.keySet()) { + assertTrue(bs.get(key)); + size2++; + assertTrue(previousKey < 0 || + (ascending ? key - previousKey > 0 : key - previousKey < 0)); + previousKey = key; + } + assertEquals(size2, size); + + // Test navigation ops + for (int key = min - 1; key <= max + 1; key++) { + assertEq(map.lowerKey(key), rs.lower(key)); + assertEq(map.floorKey(key), rs.floor(key)); + assertEq(map.higherKey(key), rs.higher(key)); + assertEq(map.ceilingKey(key), rs.ceiling(key)); + } + + // Test extrema + if (map.size() != 0) { + assertEq(map.firstKey(), rs.first()); + assertEq(map.lastKey(), rs.last()); + } else { + assertEq(rs.first(), -1); + assertEq(rs.last(), -1); + try { + map.firstKey(); + shouldThrow(); + } catch (NoSuchElementException success) {} + try { + map.lastKey(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + } + + static void assertEq(Integer i, int j) { + if (i == null) + assertEquals(j, -1); + else + assertEquals((int) i, j); + } + + static boolean eq(Integer i, int j) { + return i == null ? j == -1 : i == j; + } + +} diff --git a/jdk/test/java/util/concurrent/tck/TreeSetTest.java b/jdk/test/java/util/concurrent/tck/TreeSetTest.java new file mode 100644 index 00000000000..eb7b6efd9d0 --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/TreeSetTest.java @@ -0,0 +1,1008 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NavigableSet; +import java.util.NoSuchElementException; +import java.util.Random; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class TreeSetTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(TreeSetTest.class); + } + + static class MyReverseComparator implements Comparator { + public int compare(Object x, Object y) { + return ((Comparable)y).compareTo(x); + } + } + + /** + * The number of elements to place in collections, arrays, etc. + */ + static final int SIZE = 20; + + /** + * Returns a new set of given size containing consecutive + * Integers 0 ... n. + */ + private TreeSet populatedSet(int n) { + TreeSet q = new TreeSet(); + assertTrue(q.isEmpty()); + for (int i = n - 1; i >= 0; i -= 2) + assertTrue(q.add(new Integer(i))); + for (int i = (n & 1); i < n; i += 2) + assertTrue(q.add(new Integer(i))); + assertFalse(q.isEmpty()); + assertEquals(n, q.size()); + return q; + } + + /** + * Returns a new set of first 5 ints. + */ + private TreeSet set5() { + TreeSet q = new TreeSet(); + assertTrue(q.isEmpty()); + q.add(one); + q.add(two); + q.add(three); + q.add(four); + q.add(five); + assertEquals(5, q.size()); + return q; + } + + /** + * A new set has unbounded capacity + */ + public void testConstructor1() { + assertEquals(0, new TreeSet().size()); + } + + /** + * Initializing from null Collection throws NPE + */ + public void testConstructor3() { + try { + new TreeSet((Collection)null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing from Collection of null elements throws NPE + */ + public void testConstructor4() { + try { + new TreeSet(Arrays.asList(new Integer[SIZE])); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Initializing from Collection with some null elements throws NPE + */ + public void testConstructor5() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); + try { + new TreeSet(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Set contains all elements of collection used to initialize + */ + public void testConstructor6() { + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(i); + TreeSet q = new TreeSet(Arrays.asList(ints)); + for (int i = 0; i < SIZE; ++i) + assertEquals(ints[i], q.pollFirst()); + } + + /** + * The comparator used in constructor is used + */ + public void testConstructor7() { + MyReverseComparator cmp = new MyReverseComparator(); + TreeSet q = new TreeSet(cmp); + assertEquals(cmp, q.comparator()); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(i); + q.addAll(Arrays.asList(ints)); + for (int i = SIZE - 1; i >= 0; --i) + assertEquals(ints[i], q.pollFirst()); + } + + /** + * isEmpty is true before add, false after + */ + public void testEmpty() { + TreeSet q = new TreeSet(); + assertTrue(q.isEmpty()); + q.add(new Integer(1)); + assertFalse(q.isEmpty()); + q.add(new Integer(2)); + q.pollFirst(); + q.pollFirst(); + assertTrue(q.isEmpty()); + } + + /** + * size changes when elements added and removed + */ + public void testSize() { + TreeSet q = populatedSet(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(SIZE - i, q.size()); + q.pollFirst(); + } + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.size()); + q.add(new Integer(i)); + } + } + + /** + * add(null) throws NPE if nonempty + */ + public void testAddNull() { + TreeSet q = populatedSet(SIZE); + try { + q.add(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Add of comparable element succeeds + */ + public void testAdd() { + TreeSet q = new TreeSet(); + assertTrue(q.add(zero)); + assertTrue(q.add(one)); + } + + /** + * Add of duplicate element fails + */ + public void testAddDup() { + TreeSet q = new TreeSet(); + assertTrue(q.add(zero)); + assertFalse(q.add(zero)); + } + + /** + * Add of non-Comparable throws CCE + */ + public void testAddNonComparable() { + TreeSet q = new TreeSet(); + try { + q.add(new Object()); + q.add(new Object()); + shouldThrow(); + } catch (ClassCastException success) {} + } + + /** + * addAll(null) throws NPE + */ + public void testAddAll1() { + TreeSet q = new TreeSet(); + try { + q.addAll(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll of a collection with null elements throws NPE + */ + public void testAddAll2() { + TreeSet q = new TreeSet(); + Integer[] ints = new Integer[SIZE]; + try { + q.addAll(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll of a collection with any null elements throws NPE after + * possibly adding some elements + */ + public void testAddAll3() { + TreeSet q = new TreeSet(); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i); + try { + q.addAll(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Set contains all elements of successful addAll + */ + public void testAddAll5() { + Integer[] empty = new Integer[0]; + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(SIZE - 1 - i); + TreeSet q = new TreeSet(); + assertFalse(q.addAll(Arrays.asList(empty))); + assertTrue(q.addAll(Arrays.asList(ints))); + for (int i = 0; i < SIZE; ++i) + assertEquals(new Integer(i), q.pollFirst()); + } + + /** + * pollFirst succeeds unless empty + */ + public void testPollFirst() { + TreeSet q = populatedSet(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.pollFirst()); + } + assertNull(q.pollFirst()); + } + + /** + * pollLast succeeds unless empty + */ + public void testPollLast() { + TreeSet q = populatedSet(SIZE); + for (int i = SIZE - 1; i >= 0; --i) { + assertEquals(i, q.pollLast()); + } + assertNull(q.pollFirst()); + } + + /** + * remove(x) removes x and returns true if present + */ + public void testRemoveElement() { + TreeSet q = populatedSet(SIZE); + for (int i = 1; i < SIZE; i += 2) { + assertTrue(q.contains(i)); + assertTrue(q.remove(i)); + assertFalse(q.contains(i)); + assertTrue(q.contains(i - 1)); + } + for (int i = 0; i < SIZE; i += 2) { + assertTrue(q.contains(i)); + assertTrue(q.remove(i)); + assertFalse(q.contains(i)); + assertFalse(q.remove(i + 1)); + assertFalse(q.contains(i + 1)); + } + assertTrue(q.isEmpty()); + } + + /** + * contains(x) reports true when elements added but not yet removed + */ + public void testContains() { + TreeSet q = populatedSet(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.contains(new Integer(i))); + q.pollFirst(); + assertFalse(q.contains(new Integer(i))); + } + } + + /** + * clear removes all elements + */ + public void testClear() { + TreeSet q = populatedSet(SIZE); + q.clear(); + assertTrue(q.isEmpty()); + assertEquals(0, q.size()); + q.add(new Integer(1)); + assertFalse(q.isEmpty()); + q.clear(); + assertTrue(q.isEmpty()); + } + + /** + * containsAll(c) is true when c contains a subset of elements + */ + public void testContainsAll() { + TreeSet q = populatedSet(SIZE); + TreeSet p = new TreeSet(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.containsAll(p)); + assertFalse(p.containsAll(q)); + p.add(new Integer(i)); + } + assertTrue(p.containsAll(q)); + } + + /** + * retainAll(c) retains only those elements of c and reports true if changed + */ + public void testRetainAll() { + TreeSet q = populatedSet(SIZE); + TreeSet p = populatedSet(SIZE); + for (int i = 0; i < SIZE; ++i) { + boolean changed = q.retainAll(p); + if (i == 0) + assertFalse(changed); + else + assertTrue(changed); + + assertTrue(q.containsAll(p)); + assertEquals(SIZE - i, q.size()); + p.pollFirst(); + } + } + + /** + * removeAll(c) removes only those elements of c and reports true if changed + */ + public void testRemoveAll() { + for (int i = 1; i < SIZE; ++i) { + TreeSet q = populatedSet(SIZE); + TreeSet p = populatedSet(i); + assertTrue(q.removeAll(p)); + assertEquals(SIZE - i, q.size()); + for (int j = 0; j < i; ++j) { + Integer x = (Integer)(p.pollFirst()); + assertFalse(q.contains(x)); + } + } + } + + /** + * lower returns preceding element + */ + public void testLower() { + TreeSet q = set5(); + Object e1 = q.lower(three); + assertEquals(two, e1); + + Object e2 = q.lower(six); + assertEquals(five, e2); + + Object e3 = q.lower(one); + assertNull(e3); + + Object e4 = q.lower(zero); + assertNull(e4); + } + + /** + * higher returns next element + */ + public void testHigher() { + TreeSet q = set5(); + Object e1 = q.higher(three); + assertEquals(four, e1); + + Object e2 = q.higher(zero); + assertEquals(one, e2); + + Object e3 = q.higher(five); + assertNull(e3); + + Object e4 = q.higher(six); + assertNull(e4); + } + + /** + * floor returns preceding element + */ + public void testFloor() { + TreeSet q = set5(); + Object e1 = q.floor(three); + assertEquals(three, e1); + + Object e2 = q.floor(six); + assertEquals(five, e2); + + Object e3 = q.floor(one); + assertEquals(one, e3); + + Object e4 = q.floor(zero); + assertNull(e4); + } + + /** + * ceiling returns next element + */ + public void testCeiling() { + TreeSet q = set5(); + Object e1 = q.ceiling(three); + assertEquals(three, e1); + + Object e2 = q.ceiling(zero); + assertEquals(one, e2); + + Object e3 = q.ceiling(five); + assertEquals(five, e3); + + Object e4 = q.ceiling(six); + assertNull(e4); + } + + /** + * toArray contains all elements in sorted order + */ + public void testToArray() { + TreeSet q = populatedSet(SIZE); + Object[] o = q.toArray(); + for (int i = 0; i < o.length; i++) + assertSame(o[i], q.pollFirst()); + } + + /** + * toArray(a) contains all elements in sorted order + */ + public void testToArray2() { + TreeSet q = populatedSet(SIZE); + Integer[] ints = new Integer[SIZE]; + Integer[] array = q.toArray(ints); + assertSame(ints, array); + for (int i = 0; i < ints.length; i++) + assertSame(ints[i], q.pollFirst()); + } + + /** + * iterator iterates through all elements + */ + public void testIterator() { + TreeSet q = populatedSet(SIZE); + Iterator it = q.iterator(); + int i; + for (i = 0; it.hasNext(); i++) + assertTrue(q.contains(it.next())); + assertEquals(i, SIZE); + assertIteratorExhausted(it); + } + + /** + * iterator of empty set has no elements + */ + public void testEmptyIterator() { + assertIteratorExhausted(new TreeSet().iterator()); + } + + /** + * iterator.remove removes current element + */ + public void testIteratorRemove() { + final TreeSet q = new TreeSet(); + q.add(new Integer(2)); + q.add(new Integer(1)); + q.add(new Integer(3)); + + Iterator it = q.iterator(); + it.next(); + it.remove(); + + it = q.iterator(); + assertEquals(it.next(), new Integer(2)); + assertEquals(it.next(), new Integer(3)); + assertFalse(it.hasNext()); + } + + /** + * toString contains toStrings of elements + */ + public void testToString() { + TreeSet q = populatedSet(SIZE); + String s = q.toString(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(s.contains(String.valueOf(i))); + } + } + + /** + * A deserialized serialized set has same elements + */ + public void testSerialization() throws Exception { + NavigableSet x = populatedSet(SIZE); + NavigableSet y = serialClone(x); + + assertNotSame(x, y); + assertEquals(x.size(), y.size()); + assertEquals(x, y); + assertEquals(y, x); + while (!x.isEmpty()) { + assertFalse(y.isEmpty()); + assertEquals(x.pollFirst(), y.pollFirst()); + } + assertTrue(y.isEmpty()); + } + + /** + * subSet returns set with keys in requested range + */ + public void testSubSetContents() { + TreeSet set = set5(); + SortedSet sm = set.subSet(two, four); + assertEquals(two, sm.first()); + assertEquals(three, sm.last()); + assertEquals(2, sm.size()); + assertFalse(sm.contains(one)); + assertTrue(sm.contains(two)); + assertTrue(sm.contains(three)); + assertFalse(sm.contains(four)); + assertFalse(sm.contains(five)); + Iterator i = sm.iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(two, k); + k = (Integer)(i.next()); + assertEquals(three, k); + assertFalse(i.hasNext()); + Iterator j = sm.iterator(); + j.next(); + j.remove(); + assertFalse(set.contains(two)); + assertEquals(4, set.size()); + assertEquals(1, sm.size()); + assertEquals(three, sm.first()); + assertEquals(three, sm.last()); + assertTrue(sm.remove(three)); + assertTrue(sm.isEmpty()); + assertEquals(3, set.size()); + } + + public void testSubSetContents2() { + TreeSet set = set5(); + SortedSet sm = set.subSet(two, three); + assertEquals(1, sm.size()); + assertEquals(two, sm.first()); + assertEquals(two, sm.last()); + assertFalse(sm.contains(one)); + assertTrue(sm.contains(two)); + assertFalse(sm.contains(three)); + assertFalse(sm.contains(four)); + assertFalse(sm.contains(five)); + Iterator i = sm.iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(two, k); + assertFalse(i.hasNext()); + Iterator j = sm.iterator(); + j.next(); + j.remove(); + assertFalse(set.contains(two)); + assertEquals(4, set.size()); + assertEquals(0, sm.size()); + assertTrue(sm.isEmpty()); + assertFalse(sm.remove(three)); + assertEquals(4, set.size()); + } + + /** + * headSet returns set with keys in requested range + */ + public void testHeadSetContents() { + TreeSet set = set5(); + SortedSet sm = set.headSet(four); + assertTrue(sm.contains(one)); + assertTrue(sm.contains(two)); + assertTrue(sm.contains(three)); + assertFalse(sm.contains(four)); + assertFalse(sm.contains(five)); + Iterator i = sm.iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(one, k); + k = (Integer)(i.next()); + assertEquals(two, k); + k = (Integer)(i.next()); + assertEquals(three, k); + assertFalse(i.hasNext()); + sm.clear(); + assertTrue(sm.isEmpty()); + assertEquals(2, set.size()); + assertEquals(four, set.first()); + } + + /** + * tailSet returns set with keys in requested range + */ + public void testTailSetContents() { + TreeSet set = set5(); + SortedSet sm = set.tailSet(two); + assertFalse(sm.contains(one)); + assertTrue(sm.contains(two)); + assertTrue(sm.contains(three)); + assertTrue(sm.contains(four)); + assertTrue(sm.contains(five)); + Iterator i = sm.iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(two, k); + k = (Integer)(i.next()); + assertEquals(three, k); + k = (Integer)(i.next()); + assertEquals(four, k); + k = (Integer)(i.next()); + assertEquals(five, k); + assertFalse(i.hasNext()); + + SortedSet ssm = sm.tailSet(four); + assertEquals(four, ssm.first()); + assertEquals(five, ssm.last()); + assertTrue(ssm.remove(four)); + assertEquals(1, ssm.size()); + assertEquals(3, sm.size()); + assertEquals(4, set.size()); + } + + Random rnd = new Random(666); + BitSet bs; + + /** + * Subsets of subsets subdivide correctly + */ + public void testRecursiveSubSets() throws Exception { + int setSize = expensiveTests ? 1000 : 100; + Class cl = TreeSet.class; + + NavigableSet set = newSet(cl); + bs = new BitSet(setSize); + + populate(set, setSize); + check(set, 0, setSize - 1, true); + check(set.descendingSet(), 0, setSize - 1, false); + + mutateSet(set, 0, setSize - 1); + check(set, 0, setSize - 1, true); + check(set.descendingSet(), 0, setSize - 1, false); + + bashSubSet(set.subSet(0, true, setSize, false), + 0, setSize - 1, true); + } + + /** + * addAll is idempotent + */ + public void testAddAll_idempotent() throws Exception { + Set x = populatedSet(SIZE); + Set y = new TreeSet(x); + y.addAll(x); + assertEquals(x, y); + assertEquals(y, x); + } + + static NavigableSet newSet(Class cl) throws Exception { + NavigableSet result = (NavigableSet) cl.newInstance(); + assertEquals(0, result.size()); + assertFalse(result.iterator().hasNext()); + return result; + } + + void populate(NavigableSet set, int limit) { + for (int i = 0, n = 2 * limit / 3; i < n; i++) { + int element = rnd.nextInt(limit); + put(set, element); + } + } + + void mutateSet(NavigableSet set, int min, int max) { + int size = set.size(); + int rangeSize = max - min + 1; + + // Remove a bunch of entries directly + for (int i = 0, n = rangeSize / 2; i < n; i++) { + remove(set, min - 5 + rnd.nextInt(rangeSize + 10)); + } + + // Remove a bunch of entries with iterator + for (Iterator it = set.iterator(); it.hasNext(); ) { + if (rnd.nextBoolean()) { + bs.clear(it.next()); + it.remove(); + } + } + + // Add entries till we're back to original size + while (set.size() < size) { + int element = min + rnd.nextInt(rangeSize); + assertTrue(element >= min && element <= max); + put(set, element); + } + } + + void mutateSubSet(NavigableSet set, int min, int max) { + int size = set.size(); + int rangeSize = max - min + 1; + + // Remove a bunch of entries directly + for (int i = 0, n = rangeSize / 2; i < n; i++) { + remove(set, min - 5 + rnd.nextInt(rangeSize + 10)); + } + + // Remove a bunch of entries with iterator + for (Iterator it = set.iterator(); it.hasNext(); ) { + if (rnd.nextBoolean()) { + bs.clear(it.next()); + it.remove(); + } + } + + // Add entries till we're back to original size + while (set.size() < size) { + int element = min - 5 + rnd.nextInt(rangeSize + 10); + if (element >= min && element <= max) { + put(set, element); + } else { + try { + set.add(element); + shouldThrow(); + } catch (IllegalArgumentException success) {} + } + } + } + + void put(NavigableSet set, int element) { + if (set.add(element)) + bs.set(element); + } + + void remove(NavigableSet set, int element) { + if (set.remove(element)) + bs.clear(element); + } + + void bashSubSet(NavigableSet set, + int min, int max, boolean ascending) { + check(set, min, max, ascending); + check(set.descendingSet(), min, max, !ascending); + + mutateSubSet(set, min, max); + check(set, min, max, ascending); + check(set.descendingSet(), min, max, !ascending); + + // Recurse + if (max - min < 2) + return; + int midPoint = (min + max) / 2; + + // headSet - pick direction and endpoint inclusion randomly + boolean incl = rnd.nextBoolean(); + NavigableSet hm = set.headSet(midPoint, incl); + if (ascending) { + if (rnd.nextBoolean()) + bashSubSet(hm, min, midPoint - (incl ? 0 : 1), true); + else + bashSubSet(hm.descendingSet(), min, midPoint - (incl ? 0 : 1), + false); + } else { + if (rnd.nextBoolean()) + bashSubSet(hm, midPoint + (incl ? 0 : 1), max, false); + else + bashSubSet(hm.descendingSet(), midPoint + (incl ? 0 : 1), max, + true); + } + + // tailSet - pick direction and endpoint inclusion randomly + incl = rnd.nextBoolean(); + NavigableSet tm = set.tailSet(midPoint,incl); + if (ascending) { + if (rnd.nextBoolean()) + bashSubSet(tm, midPoint + (incl ? 0 : 1), max, true); + else + bashSubSet(tm.descendingSet(), midPoint + (incl ? 0 : 1), max, + false); + } else { + if (rnd.nextBoolean()) { + bashSubSet(tm, min, midPoint - (incl ? 0 : 1), false); + } else { + bashSubSet(tm.descendingSet(), min, midPoint - (incl ? 0 : 1), + true); + } + } + + // subSet - pick direction and endpoint inclusion randomly + int rangeSize = max - min + 1; + int[] endpoints = new int[2]; + endpoints[0] = min + rnd.nextInt(rangeSize); + endpoints[1] = min + rnd.nextInt(rangeSize); + Arrays.sort(endpoints); + boolean lowIncl = rnd.nextBoolean(); + boolean highIncl = rnd.nextBoolean(); + if (ascending) { + NavigableSet sm = set.subSet( + endpoints[0], lowIncl, endpoints[1], highIncl); + if (rnd.nextBoolean()) + bashSubSet(sm, endpoints[0] + (lowIncl ? 0 : 1), + endpoints[1] - (highIncl ? 0 : 1), true); + else + bashSubSet(sm.descendingSet(), endpoints[0] + (lowIncl ? 0 : 1), + endpoints[1] - (highIncl ? 0 : 1), false); + } else { + NavigableSet sm = set.subSet( + endpoints[1], highIncl, endpoints[0], lowIncl); + if (rnd.nextBoolean()) + bashSubSet(sm, endpoints[0] + (lowIncl ? 0 : 1), + endpoints[1] - (highIncl ? 0 : 1), false); + else + bashSubSet(sm.descendingSet(), endpoints[0] + (lowIncl ? 0 : 1), + endpoints[1] - (highIncl ? 0 : 1), true); + } + } + + /** + * min and max are both inclusive. If max < min, interval is empty. + */ + void check(NavigableSet set, + final int min, final int max, final boolean ascending) { + class ReferenceSet { + int lower(int element) { + return ascending ? + lowerAscending(element) : higherAscending(element); + } + int floor(int element) { + return ascending ? + floorAscending(element) : ceilingAscending(element); + } + int ceiling(int element) { + return ascending ? + ceilingAscending(element) : floorAscending(element); + } + int higher(int element) { + return ascending ? + higherAscending(element) : lowerAscending(element); + } + int first() { + return ascending ? firstAscending() : lastAscending(); + } + int last() { + return ascending ? lastAscending() : firstAscending(); + } + int lowerAscending(int element) { + return floorAscending(element - 1); + } + int floorAscending(int element) { + if (element < min) + return -1; + else if (element > max) + element = max; + + // BitSet should support this! Test would run much faster + while (element >= min) { + if (bs.get(element)) + return element; + element--; + } + return -1; + } + int ceilingAscending(int element) { + if (element < min) + element = min; + else if (element > max) + return -1; + int result = bs.nextSetBit(element); + return (result > max) ? -1 : result; + } + int higherAscending(int element) { + return ceilingAscending(element + 1); + } + private int firstAscending() { + int result = ceilingAscending(min); + return (result > max) ? -1 : result; + } + private int lastAscending() { + int result = floorAscending(max); + return (result < min) ? -1 : result; + } + } + ReferenceSet rs = new ReferenceSet(); + + // Test contents using containsElement + int size = 0; + for (int i = min; i <= max; i++) { + boolean bsContainsI = bs.get(i); + assertEquals(bsContainsI, set.contains(i)); + if (bsContainsI) + size++; + } + assertEquals(size, set.size()); + + // Test contents using contains elementSet iterator + int size2 = 0; + int previousElement = -1; + for (int element : set) { + assertTrue(bs.get(element)); + size2++; + assertTrue(previousElement < 0 || (ascending ? + element - previousElement > 0 : element - previousElement < 0)); + previousElement = element; + } + assertEquals(size2, size); + + // Test navigation ops + for (int element = min - 1; element <= max + 1; element++) { + assertEq(set.lower(element), rs.lower(element)); + assertEq(set.floor(element), rs.floor(element)); + assertEq(set.higher(element), rs.higher(element)); + assertEq(set.ceiling(element), rs.ceiling(element)); + } + + // Test extrema + if (set.size() != 0) { + assertEq(set.first(), rs.first()); + assertEq(set.last(), rs.last()); + } else { + assertEq(rs.first(), -1); + assertEq(rs.last(), -1); + try { + set.first(); + shouldThrow(); + } catch (NoSuchElementException success) {} + try { + set.last(); + shouldThrow(); + } catch (NoSuchElementException success) {} + } + } + + static void assertEq(Integer i, int j) { + if (i == null) + assertEquals(j, -1); + else + assertEquals((int) i, j); + } + + static boolean eq(Integer i, int j) { + return (i == null) ? j == -1 : i == j; + } + +} diff --git a/jdk/test/java/util/concurrent/tck/TreeSubMapTest.java b/jdk/test/java/util/concurrent/tck/TreeSubMapTest.java new file mode 100644 index 00000000000..1b5fa424b1c --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/TreeSubMapTest.java @@ -0,0 +1,1138 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.NavigableMap; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class TreeSubMapTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(TreeSubMapTest.class); + } + + /** + * Returns a new map from Integers 1-5 to Strings "A"-"E". + */ + private static NavigableMap map5() { + TreeMap map = new TreeMap(); + assertTrue(map.isEmpty()); + map.put(zero, "Z"); + map.put(one, "A"); + map.put(five, "E"); + map.put(three, "C"); + map.put(two, "B"); + map.put(four, "D"); + map.put(seven, "F"); + assertFalse(map.isEmpty()); + assertEquals(7, map.size()); + return map.subMap(one, true, seven, false); + } + + private static NavigableMap map0() { + TreeMap map = new TreeMap(); + assertTrue(map.isEmpty()); + return map.tailMap(one, true); + } + + /** + * Returns a new map from Integers -5 to -1 to Strings "A"-"E". + */ + private static NavigableMap dmap5() { + TreeMap map = new TreeMap(); + assertTrue(map.isEmpty()); + map.put(m1, "A"); + map.put(m5, "E"); + map.put(m3, "C"); + map.put(m2, "B"); + map.put(m4, "D"); + assertFalse(map.isEmpty()); + assertEquals(5, map.size()); + return map.descendingMap(); + } + + private static NavigableMap dmap0() { + TreeMap map = new TreeMap(); + assertTrue(map.isEmpty()); + return map; + } + + /** + * clear removes all pairs + */ + public void testClear() { + NavigableMap map = map5(); + map.clear(); + assertEquals(0, map.size()); + } + + /** + * Maps with same contents are equal + */ + public void testEquals() { + NavigableMap map1 = map5(); + NavigableMap map2 = map5(); + assertEquals(map1, map2); + assertEquals(map2, map1); + map1.clear(); + assertFalse(map1.equals(map2)); + assertFalse(map2.equals(map1)); + } + + /** + * containsKey returns true for contained key + */ + public void testContainsKey() { + NavigableMap map = map5(); + assertTrue(map.containsKey(one)); + assertFalse(map.containsKey(zero)); + } + + /** + * containsValue returns true for held values + */ + public void testContainsValue() { + NavigableMap map = map5(); + assertTrue(map.containsValue("A")); + assertFalse(map.containsValue("Z")); + } + + /** + * get returns the correct element at the given key, + * or null if not present + */ + public void testGet() { + NavigableMap map = map5(); + assertEquals("A", (String)map.get(one)); + NavigableMap empty = map0(); + assertNull(empty.get(one)); + } + + /** + * isEmpty is true of empty map and false for non-empty + */ + public void testIsEmpty() { + NavigableMap empty = map0(); + NavigableMap map = map5(); + assertTrue(empty.isEmpty()); + assertFalse(map.isEmpty()); + } + + /** + * firstKey returns first key + */ + public void testFirstKey() { + NavigableMap map = map5(); + assertEquals(one, map.firstKey()); + } + + /** + * lastKey returns last key + */ + public void testLastKey() { + NavigableMap map = map5(); + assertEquals(five, map.lastKey()); + } + + /** + * keySet returns a Set containing all the keys + */ + public void testKeySet() { + NavigableMap map = map5(); + Set s = map.keySet(); + assertEquals(5, s.size()); + assertTrue(s.contains(one)); + assertTrue(s.contains(two)); + assertTrue(s.contains(three)); + assertTrue(s.contains(four)); + assertTrue(s.contains(five)); + } + + /** + * keySet is ordered + */ + public void testKeySetOrder() { + NavigableMap map = map5(); + Set s = map.keySet(); + Iterator i = s.iterator(); + Integer last = (Integer)i.next(); + assertEquals(last, one); + while (i.hasNext()) { + Integer k = (Integer)i.next(); + assertTrue(last.compareTo(k) < 0); + last = k; + } + } + + /** + * values collection contains all values + */ + public void testValues() { + NavigableMap map = map5(); + Collection s = map.values(); + assertEquals(5, s.size()); + assertTrue(s.contains("A")); + assertTrue(s.contains("B")); + assertTrue(s.contains("C")); + assertTrue(s.contains("D")); + assertTrue(s.contains("E")); + } + + /** + * entrySet contains all pairs + */ + public void testEntrySet() { + NavigableMap map = map5(); + Set s = map.entrySet(); + assertEquals(5, s.size()); + Iterator it = s.iterator(); + while (it.hasNext()) { + Map.Entry e = (Map.Entry) it.next(); + assertTrue( + (e.getKey().equals(one) && e.getValue().equals("A")) || + (e.getKey().equals(two) && e.getValue().equals("B")) || + (e.getKey().equals(three) && e.getValue().equals("C")) || + (e.getKey().equals(four) && e.getValue().equals("D")) || + (e.getKey().equals(five) && e.getValue().equals("E"))); + } + } + + /** + * putAll adds all key-value pairs from the given map + */ + public void testPutAll() { + NavigableMap empty = map0(); + NavigableMap map = map5(); + empty.putAll(map); + assertEquals(5, empty.size()); + assertTrue(empty.containsKey(one)); + assertTrue(empty.containsKey(two)); + assertTrue(empty.containsKey(three)); + assertTrue(empty.containsKey(four)); + assertTrue(empty.containsKey(five)); + } + + /** + * remove removes the correct key-value pair from the map + */ + public void testRemove() { + NavigableMap map = map5(); + map.remove(five); + assertEquals(4, map.size()); + assertFalse(map.containsKey(five)); + } + + /** + * lowerEntry returns preceding entry. + */ + public void testLowerEntry() { + NavigableMap map = map5(); + Map.Entry e1 = map.lowerEntry(three); + assertEquals(two, e1.getKey()); + + Map.Entry e2 = map.lowerEntry(six); + assertEquals(five, e2.getKey()); + + Map.Entry e3 = map.lowerEntry(one); + assertNull(e3); + + Map.Entry e4 = map.lowerEntry(zero); + assertNull(e4); + } + + /** + * higherEntry returns next entry. + */ + public void testHigherEntry() { + NavigableMap map = map5(); + Map.Entry e1 = map.higherEntry(three); + assertEquals(four, e1.getKey()); + + Map.Entry e2 = map.higherEntry(zero); + assertEquals(one, e2.getKey()); + + Map.Entry e3 = map.higherEntry(five); + assertNull(e3); + + Map.Entry e4 = map.higherEntry(six); + assertNull(e4); + } + + /** + * floorEntry returns preceding entry. + */ + public void testFloorEntry() { + NavigableMap map = map5(); + Map.Entry e1 = map.floorEntry(three); + assertEquals(three, e1.getKey()); + + Map.Entry e2 = map.floorEntry(six); + assertEquals(five, e2.getKey()); + + Map.Entry e3 = map.floorEntry(one); + assertEquals(one, e3.getKey()); + + Map.Entry e4 = map.floorEntry(zero); + assertNull(e4); + } + + /** + * ceilingEntry returns next entry. + */ + public void testCeilingEntry() { + NavigableMap map = map5(); + Map.Entry e1 = map.ceilingEntry(three); + assertEquals(three, e1.getKey()); + + Map.Entry e2 = map.ceilingEntry(zero); + assertEquals(one, e2.getKey()); + + Map.Entry e3 = map.ceilingEntry(five); + assertEquals(five, e3.getKey()); + + Map.Entry e4 = map.ceilingEntry(six); + assertNull(e4); + } + + /** + * pollFirstEntry returns entries in order + */ + public void testPollFirstEntry() { + NavigableMap map = map5(); + Map.Entry e = map.pollFirstEntry(); + assertEquals(one, e.getKey()); + assertEquals("A", e.getValue()); + e = map.pollFirstEntry(); + assertEquals(two, e.getKey()); + map.put(one, "A"); + e = map.pollFirstEntry(); + assertEquals(one, e.getKey()); + assertEquals("A", e.getValue()); + e = map.pollFirstEntry(); + assertEquals(three, e.getKey()); + map.remove(four); + e = map.pollFirstEntry(); + assertEquals(five, e.getKey()); + try { + e.setValue("A"); + shouldThrow(); + } catch (UnsupportedOperationException success) {} + assertTrue(map.isEmpty()); + Map.Entry f = map.firstEntry(); + assertNull(f); + e = map.pollFirstEntry(); + assertNull(e); + } + + /** + * pollLastEntry returns entries in order + */ + public void testPollLastEntry() { + NavigableMap map = map5(); + Map.Entry e = map.pollLastEntry(); + assertEquals(five, e.getKey()); + assertEquals("E", e.getValue()); + e = map.pollLastEntry(); + assertEquals(four, e.getKey()); + map.put(five, "E"); + e = map.pollLastEntry(); + assertEquals(five, e.getKey()); + assertEquals("E", e.getValue()); + e = map.pollLastEntry(); + assertEquals(three, e.getKey()); + map.remove(two); + e = map.pollLastEntry(); + assertEquals(one, e.getKey()); + try { + e.setValue("E"); + shouldThrow(); + } catch (UnsupportedOperationException success) {} + e = map.pollLastEntry(); + assertNull(e); + } + + /** + * size returns the correct values + */ + public void testSize() { + NavigableMap map = map5(); + NavigableMap empty = map0(); + assertEquals(0, empty.size()); + assertEquals(5, map.size()); + } + + /** + * toString contains toString of elements + */ + public void testToString() { + NavigableMap map = map5(); + String s = map.toString(); + for (int i = 1; i <= 5; ++i) { + assertTrue(s.contains(String.valueOf(i))); + } + } + + // Exception tests + + /** + * get(null) of nonempty map throws NPE + */ + public void testGet_NullPointerException() { + NavigableMap c = map5(); + try { + c.get(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * containsKey(null) of nonempty map throws NPE + */ + public void testContainsKey_NullPointerException() { + NavigableMap c = map5(); + try { + c.containsKey(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * put(null,x) throws NPE + */ + public void testPut1_NullPointerException() { + NavigableMap c = map5(); + try { + c.put(null, "whatever"); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * remove(null) throws NPE + */ + public void testRemove1_NullPointerException() { + NavigableMap c = map5(); + try { + c.remove(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * A deserialized map equals original + */ + public void testSerialization() throws Exception { + NavigableMap x = map5(); + NavigableMap y = serialClone(x); + + assertNotSame(x, y); + assertEquals(x.size(), y.size()); + assertEquals(x.toString(), y.toString()); + assertEquals(x, y); + assertEquals(y, x); + } + + /** + * subMap returns map with keys in requested range + */ + public void testSubMapContents() { + NavigableMap map = map5(); + SortedMap sm = map.subMap(two, four); + assertEquals(two, sm.firstKey()); + assertEquals(three, sm.lastKey()); + assertEquals(2, sm.size()); + assertFalse(sm.containsKey(one)); + assertTrue(sm.containsKey(two)); + assertTrue(sm.containsKey(three)); + assertFalse(sm.containsKey(four)); + assertFalse(sm.containsKey(five)); + Iterator i = sm.keySet().iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(two, k); + k = (Integer)(i.next()); + assertEquals(three, k); + assertFalse(i.hasNext()); + Iterator j = sm.keySet().iterator(); + j.next(); + j.remove(); + assertFalse(map.containsKey(two)); + assertEquals(4, map.size()); + assertEquals(1, sm.size()); + assertEquals(three, sm.firstKey()); + assertEquals(three, sm.lastKey()); + assertEquals("C", sm.remove(three)); + assertTrue(sm.isEmpty()); + assertEquals(3, map.size()); + } + + public void testSubMapContents2() { + NavigableMap map = map5(); + SortedMap sm = map.subMap(two, three); + assertEquals(1, sm.size()); + assertEquals(two, sm.firstKey()); + assertEquals(two, sm.lastKey()); + assertFalse(sm.containsKey(one)); + assertTrue(sm.containsKey(two)); + assertFalse(sm.containsKey(three)); + assertFalse(sm.containsKey(four)); + assertFalse(sm.containsKey(five)); + Iterator i = sm.keySet().iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(two, k); + assertFalse(i.hasNext()); + Iterator j = sm.keySet().iterator(); + j.next(); + j.remove(); + assertFalse(map.containsKey(two)); + assertEquals(4, map.size()); + assertEquals(0, sm.size()); + assertTrue(sm.isEmpty()); + assertSame(sm.remove(three), null); + assertEquals(4, map.size()); + } + + /** + * headMap returns map with keys in requested range + */ + public void testHeadMapContents() { + NavigableMap map = map5(); + SortedMap sm = map.headMap(four); + assertTrue(sm.containsKey(one)); + assertTrue(sm.containsKey(two)); + assertTrue(sm.containsKey(three)); + assertFalse(sm.containsKey(four)); + assertFalse(sm.containsKey(five)); + Iterator i = sm.keySet().iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(one, k); + k = (Integer)(i.next()); + assertEquals(two, k); + k = (Integer)(i.next()); + assertEquals(three, k); + assertFalse(i.hasNext()); + sm.clear(); + assertTrue(sm.isEmpty()); + assertEquals(2, map.size()); + assertEquals(four, map.firstKey()); + } + + /** + * headMap returns map with keys in requested range + */ + public void testTailMapContents() { + NavigableMap map = map5(); + SortedMap sm = map.tailMap(two); + assertFalse(sm.containsKey(one)); + assertTrue(sm.containsKey(two)); + assertTrue(sm.containsKey(three)); + assertTrue(sm.containsKey(four)); + assertTrue(sm.containsKey(five)); + Iterator i = sm.keySet().iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(two, k); + k = (Integer)(i.next()); + assertEquals(three, k); + k = (Integer)(i.next()); + assertEquals(four, k); + k = (Integer)(i.next()); + assertEquals(five, k); + assertFalse(i.hasNext()); + + Iterator ei = sm.entrySet().iterator(); + Map.Entry e; + e = (Map.Entry)(ei.next()); + assertEquals(two, e.getKey()); + assertEquals("B", e.getValue()); + e = (Map.Entry)(ei.next()); + assertEquals(three, e.getKey()); + assertEquals("C", e.getValue()); + e = (Map.Entry)(ei.next()); + assertEquals(four, e.getKey()); + assertEquals("D", e.getValue()); + e = (Map.Entry)(ei.next()); + assertEquals(five, e.getKey()); + assertEquals("E", e.getValue()); + assertFalse(i.hasNext()); + + SortedMap ssm = sm.tailMap(four); + assertEquals(four, ssm.firstKey()); + assertEquals(five, ssm.lastKey()); + assertEquals("D", ssm.remove(four)); + assertEquals(1, ssm.size()); + assertEquals(3, sm.size()); + assertEquals(4, map.size()); + } + + /** + * clear removes all pairs + */ + public void testDescendingClear() { + NavigableMap map = dmap5(); + map.clear(); + assertEquals(0, map.size()); + } + + /** + * Maps with same contents are equal + */ + public void testDescendingEquals() { + NavigableMap map1 = dmap5(); + NavigableMap map2 = dmap5(); + assertEquals(map1, map2); + assertEquals(map2, map1); + map1.clear(); + assertFalse(map1.equals(map2)); + assertFalse(map2.equals(map1)); + } + + /** + * containsKey returns true for contained key + */ + public void testDescendingContainsKey() { + NavigableMap map = dmap5(); + assertTrue(map.containsKey(m1)); + assertFalse(map.containsKey(zero)); + } + + /** + * containsValue returns true for held values + */ + public void testDescendingContainsValue() { + NavigableMap map = dmap5(); + assertTrue(map.containsValue("A")); + assertFalse(map.containsValue("Z")); + } + + /** + * get returns the correct element at the given key, + * or null if not present + */ + public void testDescendingGet() { + NavigableMap map = dmap5(); + assertEquals("A", (String)map.get(m1)); + NavigableMap empty = dmap0(); + assertNull(empty.get(m1)); + } + + /** + * isEmpty is true of empty map and false for non-empty + */ + public void testDescendingIsEmpty() { + NavigableMap empty = dmap0(); + NavigableMap map = dmap5(); + assertTrue(empty.isEmpty()); + assertFalse(map.isEmpty()); + } + + /** + * firstKey returns first key + */ + public void testDescendingFirstKey() { + NavigableMap map = dmap5(); + assertEquals(m1, map.firstKey()); + } + + /** + * lastKey returns last key + */ + public void testDescendingLastKey() { + NavigableMap map = dmap5(); + assertEquals(m5, map.lastKey()); + } + + /** + * keySet returns a Set containing all the keys + */ + public void testDescendingKeySet() { + NavigableMap map = dmap5(); + Set s = map.keySet(); + assertEquals(5, s.size()); + assertTrue(s.contains(m1)); + assertTrue(s.contains(m2)); + assertTrue(s.contains(m3)); + assertTrue(s.contains(m4)); + assertTrue(s.contains(m5)); + } + + /** + * keySet is ordered + */ + public void testDescendingKeySetOrder() { + NavigableMap map = dmap5(); + Set s = map.keySet(); + Iterator i = s.iterator(); + Integer last = (Integer)i.next(); + assertEquals(last, m1); + while (i.hasNext()) { + Integer k = (Integer)i.next(); + assertTrue(last.compareTo(k) > 0); + last = k; + } + } + + /** + * values collection contains all values + */ + public void testDescendingValues() { + NavigableMap map = dmap5(); + Collection s = map.values(); + assertEquals(5, s.size()); + assertTrue(s.contains("A")); + assertTrue(s.contains("B")); + assertTrue(s.contains("C")); + assertTrue(s.contains("D")); + assertTrue(s.contains("E")); + } + + /** + * keySet.toArray returns contains all keys + */ + public void testDescendingAscendingKeySetToArray() { + NavigableMap map = dmap5(); + Set s = map.keySet(); + Object[] ar = s.toArray(); + assertTrue(s.containsAll(Arrays.asList(ar))); + assertEquals(5, ar.length); + ar[0] = m10; + assertFalse(s.containsAll(Arrays.asList(ar))); + } + + /** + * descendingkeySet.toArray returns contains all keys + */ + public void testDescendingDescendingKeySetToArray() { + NavigableMap map = dmap5(); + Set s = map.descendingKeySet(); + Object[] ar = s.toArray(); + assertEquals(5, ar.length); + assertTrue(s.containsAll(Arrays.asList(ar))); + ar[0] = m10; + assertFalse(s.containsAll(Arrays.asList(ar))); + } + + /** + * Values.toArray contains all values + */ + public void testDescendingValuesToArray() { + NavigableMap map = dmap5(); + Collection v = map.values(); + Object[] ar = v.toArray(); + ArrayList s = new ArrayList(Arrays.asList(ar)); + assertEquals(5, ar.length); + assertTrue(s.contains("A")); + assertTrue(s.contains("B")); + assertTrue(s.contains("C")); + assertTrue(s.contains("D")); + assertTrue(s.contains("E")); + } + + /** + * entrySet contains all pairs + */ + public void testDescendingEntrySet() { + NavigableMap map = dmap5(); + Set s = map.entrySet(); + assertEquals(5, s.size()); + Iterator it = s.iterator(); + while (it.hasNext()) { + Map.Entry e = (Map.Entry) it.next(); + assertTrue( + (e.getKey().equals(m1) && e.getValue().equals("A")) || + (e.getKey().equals(m2) && e.getValue().equals("B")) || + (e.getKey().equals(m3) && e.getValue().equals("C")) || + (e.getKey().equals(m4) && e.getValue().equals("D")) || + (e.getKey().equals(m5) && e.getValue().equals("E"))); + } + } + + /** + * putAll adds all key-value pairs from the given map + */ + public void testDescendingPutAll() { + NavigableMap empty = dmap0(); + NavigableMap map = dmap5(); + empty.putAll(map); + assertEquals(5, empty.size()); + assertTrue(empty.containsKey(m1)); + assertTrue(empty.containsKey(m2)); + assertTrue(empty.containsKey(m3)); + assertTrue(empty.containsKey(m4)); + assertTrue(empty.containsKey(m5)); + } + + /** + * remove removes the correct key-value pair from the map + */ + public void testDescendingRemove() { + NavigableMap map = dmap5(); + map.remove(m5); + assertEquals(4, map.size()); + assertFalse(map.containsKey(m5)); + } + + /** + * lowerEntry returns preceding entry. + */ + public void testDescendingLowerEntry() { + NavigableMap map = dmap5(); + Map.Entry e1 = map.lowerEntry(m3); + assertEquals(m2, e1.getKey()); + + Map.Entry e2 = map.lowerEntry(m6); + assertEquals(m5, e2.getKey()); + + Map.Entry e3 = map.lowerEntry(m1); + assertNull(e3); + + Map.Entry e4 = map.lowerEntry(zero); + assertNull(e4); + } + + /** + * higherEntry returns next entry. + */ + public void testDescendingHigherEntry() { + NavigableMap map = dmap5(); + Map.Entry e1 = map.higherEntry(m3); + assertEquals(m4, e1.getKey()); + + Map.Entry e2 = map.higherEntry(zero); + assertEquals(m1, e2.getKey()); + + Map.Entry e3 = map.higherEntry(m5); + assertNull(e3); + + Map.Entry e4 = map.higherEntry(m6); + assertNull(e4); + } + + /** + * floorEntry returns preceding entry. + */ + public void testDescendingFloorEntry() { + NavigableMap map = dmap5(); + Map.Entry e1 = map.floorEntry(m3); + assertEquals(m3, e1.getKey()); + + Map.Entry e2 = map.floorEntry(m6); + assertEquals(m5, e2.getKey()); + + Map.Entry e3 = map.floorEntry(m1); + assertEquals(m1, e3.getKey()); + + Map.Entry e4 = map.floorEntry(zero); + assertNull(e4); + } + + /** + * ceilingEntry returns next entry. + */ + public void testDescendingCeilingEntry() { + NavigableMap map = dmap5(); + Map.Entry e1 = map.ceilingEntry(m3); + assertEquals(m3, e1.getKey()); + + Map.Entry e2 = map.ceilingEntry(zero); + assertEquals(m1, e2.getKey()); + + Map.Entry e3 = map.ceilingEntry(m5); + assertEquals(m5, e3.getKey()); + + Map.Entry e4 = map.ceilingEntry(m6); + assertNull(e4); + } + + /** + * pollFirstEntry returns entries in order + */ + public void testDescendingPollFirstEntry() { + NavigableMap map = dmap5(); + Map.Entry e = map.pollFirstEntry(); + assertEquals(m1, e.getKey()); + assertEquals("A", e.getValue()); + e = map.pollFirstEntry(); + assertEquals(m2, e.getKey()); + map.put(m1, "A"); + e = map.pollFirstEntry(); + assertEquals(m1, e.getKey()); + assertEquals("A", e.getValue()); + e = map.pollFirstEntry(); + assertEquals(m3, e.getKey()); + map.remove(m4); + e = map.pollFirstEntry(); + assertEquals(m5, e.getKey()); + try { + e.setValue("A"); + shouldThrow(); + } catch (UnsupportedOperationException success) {} + e = map.pollFirstEntry(); + assertNull(e); + } + + /** + * pollLastEntry returns entries in order + */ + public void testDescendingPollLastEntry() { + NavigableMap map = dmap5(); + Map.Entry e = map.pollLastEntry(); + assertEquals(m5, e.getKey()); + assertEquals("E", e.getValue()); + e = map.pollLastEntry(); + assertEquals(m4, e.getKey()); + map.put(m5, "E"); + e = map.pollLastEntry(); + assertEquals(m5, e.getKey()); + assertEquals("E", e.getValue()); + e = map.pollLastEntry(); + assertEquals(m3, e.getKey()); + map.remove(m2); + e = map.pollLastEntry(); + assertEquals(m1, e.getKey()); + try { + e.setValue("E"); + shouldThrow(); + } catch (UnsupportedOperationException success) {} + e = map.pollLastEntry(); + assertNull(e); + } + + /** + * size returns the correct values + */ + public void testDescendingSize() { + NavigableMap map = dmap5(); + NavigableMap empty = dmap0(); + assertEquals(0, empty.size()); + assertEquals(5, map.size()); + } + + /** + * toString contains toString of elements + */ + public void testDescendingToString() { + NavigableMap map = dmap5(); + String s = map.toString(); + for (int i = 1; i <= 5; ++i) { + assertTrue(s.contains(String.valueOf(i))); + } + } + + // Exception testDescendings + + /** + * get(null) of nonempty map throws NPE + */ + public void testDescendingGet_NullPointerException() { + NavigableMap c = dmap5(); + try { + c.get(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * put(null,x) throws NPE + */ + public void testDescendingPut1_NullPointerException() { + NavigableMap c = dmap5(); + try { + c.put(null, "whatever"); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * A deserialized map equals original + */ + public void testDescendingSerialization() throws Exception { + NavigableMap x = dmap5(); + NavigableMap y = serialClone(x); + + assertNotSame(x, y); + assertEquals(x.size(), y.size()); + assertEquals(x.toString(), y.toString()); + assertEquals(x, y); + assertEquals(y, x); + } + + /** + * subMap returns map with keys in requested range + */ + public void testDescendingSubMapContents() { + NavigableMap map = dmap5(); + SortedMap sm = map.subMap(m2, m4); + assertEquals(m2, sm.firstKey()); + assertEquals(m3, sm.lastKey()); + assertEquals(2, sm.size()); + assertFalse(sm.containsKey(m1)); + assertTrue(sm.containsKey(m2)); + assertTrue(sm.containsKey(m3)); + assertFalse(sm.containsKey(m4)); + assertFalse(sm.containsKey(m5)); + Iterator i = sm.keySet().iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(m2, k); + k = (Integer)(i.next()); + assertEquals(m3, k); + assertFalse(i.hasNext()); + Iterator j = sm.keySet().iterator(); + j.next(); + j.remove(); + assertFalse(map.containsKey(m2)); + assertEquals(4, map.size()); + assertEquals(1, sm.size()); + assertEquals(m3, sm.firstKey()); + assertEquals(m3, sm.lastKey()); + assertEquals("C", sm.remove(m3)); + assertTrue(sm.isEmpty()); + assertEquals(3, map.size()); + } + + public void testDescendingSubMapContents2() { + NavigableMap map = dmap5(); + SortedMap sm = map.subMap(m2, m3); + assertEquals(1, sm.size()); + assertEquals(m2, sm.firstKey()); + assertEquals(m2, sm.lastKey()); + assertFalse(sm.containsKey(m1)); + assertTrue(sm.containsKey(m2)); + assertFalse(sm.containsKey(m3)); + assertFalse(sm.containsKey(m4)); + assertFalse(sm.containsKey(m5)); + Iterator i = sm.keySet().iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(m2, k); + assertFalse(i.hasNext()); + Iterator j = sm.keySet().iterator(); + j.next(); + j.remove(); + assertFalse(map.containsKey(m2)); + assertEquals(4, map.size()); + assertEquals(0, sm.size()); + assertTrue(sm.isEmpty()); + assertSame(sm.remove(m3), null); + assertEquals(4, map.size()); + } + + /** + * headMap returns map with keys in requested range + */ + public void testDescendingHeadMapContents() { + NavigableMap map = dmap5(); + SortedMap sm = map.headMap(m4); + assertTrue(sm.containsKey(m1)); + assertTrue(sm.containsKey(m2)); + assertTrue(sm.containsKey(m3)); + assertFalse(sm.containsKey(m4)); + assertFalse(sm.containsKey(m5)); + Iterator i = sm.keySet().iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(m1, k); + k = (Integer)(i.next()); + assertEquals(m2, k); + k = (Integer)(i.next()); + assertEquals(m3, k); + assertFalse(i.hasNext()); + sm.clear(); + assertTrue(sm.isEmpty()); + assertEquals(2, map.size()); + assertEquals(m4, map.firstKey()); + } + + /** + * headMap returns map with keys in requested range + */ + public void testDescendingTailMapContents() { + NavigableMap map = dmap5(); + SortedMap sm = map.tailMap(m2); + assertFalse(sm.containsKey(m1)); + assertTrue(sm.containsKey(m2)); + assertTrue(sm.containsKey(m3)); + assertTrue(sm.containsKey(m4)); + assertTrue(sm.containsKey(m5)); + Iterator i = sm.keySet().iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(m2, k); + k = (Integer)(i.next()); + assertEquals(m3, k); + k = (Integer)(i.next()); + assertEquals(m4, k); + k = (Integer)(i.next()); + assertEquals(m5, k); + assertFalse(i.hasNext()); + + Iterator ei = sm.entrySet().iterator(); + Map.Entry e; + e = (Map.Entry)(ei.next()); + assertEquals(m2, e.getKey()); + assertEquals("B", e.getValue()); + e = (Map.Entry)(ei.next()); + assertEquals(m3, e.getKey()); + assertEquals("C", e.getValue()); + e = (Map.Entry)(ei.next()); + assertEquals(m4, e.getKey()); + assertEquals("D", e.getValue()); + e = (Map.Entry)(ei.next()); + assertEquals(m5, e.getKey()); + assertEquals("E", e.getValue()); + assertFalse(i.hasNext()); + + SortedMap ssm = sm.tailMap(m4); + assertEquals(m4, ssm.firstKey()); + assertEquals(m5, ssm.lastKey()); + assertEquals("D", ssm.remove(m4)); + assertEquals(1, ssm.size()); + assertEquals(3, sm.size()); + assertEquals(4, map.size()); + } + +} diff --git a/jdk/test/java/util/concurrent/tck/TreeSubSetTest.java b/jdk/test/java/util/concurrent/tck/TreeSubSetTest.java new file mode 100644 index 00000000000..afc0604075d --- /dev/null +++ b/jdk/test/java/util/concurrent/tck/TreeSubSetTest.java @@ -0,0 +1,1139 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import java.util.Arrays; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NavigableSet; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class TreeSubSetTest extends JSR166TestCase { + public static void main(String[] args) { + main(suite(), args); + } + public static Test suite() { + return new TestSuite(TreeSubSetTest.class); + } + + static class MyReverseComparator implements Comparator { + public int compare(Object x, Object y) { + return ((Comparable)y).compareTo(x); + } + } + + /** + * Returns a new set of given size containing consecutive + * Integers 0 ... n. + */ + private NavigableSet populatedSet(int n) { + TreeSet q = new TreeSet(); + assertTrue(q.isEmpty()); + + for (int i = n - 1; i >= 0; i -= 2) + assertTrue(q.add(new Integer(i))); + for (int i = (n & 1); i < n; i += 2) + assertTrue(q.add(new Integer(i))); + assertTrue(q.add(new Integer(-n))); + assertTrue(q.add(new Integer(n))); + NavigableSet s = q.subSet(new Integer(0), true, new Integer(n), false); + assertFalse(s.isEmpty()); + assertEquals(n, s.size()); + return s; + } + + /** + * Returns a new set of first 5 ints. + */ + private NavigableSet set5() { + TreeSet q = new TreeSet(); + assertTrue(q.isEmpty()); + q.add(one); + q.add(two); + q.add(three); + q.add(four); + q.add(five); + q.add(zero); + q.add(seven); + NavigableSet s = q.subSet(one, true, seven, false); + assertEquals(5, s.size()); + return s; + } + + private NavigableSet dset5() { + TreeSet q = new TreeSet(); + assertTrue(q.isEmpty()); + q.add(m1); + q.add(m2); + q.add(m3); + q.add(m4); + q.add(m5); + NavigableSet s = q.descendingSet(); + assertEquals(5, s.size()); + return s; + } + + private static NavigableSet set0() { + TreeSet set = new TreeSet(); + assertTrue(set.isEmpty()); + return set.tailSet(m1, false); + } + + private static NavigableSet dset0() { + TreeSet set = new TreeSet(); + assertTrue(set.isEmpty()); + return set; + } + + /** + * A new set has unbounded capacity + */ + public void testConstructor1() { + assertEquals(0, set0().size()); + } + + /** + * isEmpty is true before add, false after + */ + public void testEmpty() { + NavigableSet q = set0(); + assertTrue(q.isEmpty()); + assertTrue(q.add(new Integer(1))); + assertFalse(q.isEmpty()); + assertTrue(q.add(new Integer(2))); + q.pollFirst(); + q.pollFirst(); + assertTrue(q.isEmpty()); + } + + /** + * size changes when elements added and removed + */ + public void testSize() { + NavigableSet q = populatedSet(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(SIZE - i, q.size()); + q.pollFirst(); + } + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.size()); + q.add(new Integer(i)); + } + } + + /** + * add(null) throws NPE + */ + public void testAddNull() { + NavigableSet q = set0(); + try { + q.add(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Add of comparable element succeeds + */ + public void testAdd() { + NavigableSet q = set0(); + assertTrue(q.add(six)); + } + + /** + * Add of duplicate element fails + */ + public void testAddDup() { + NavigableSet q = set0(); + assertTrue(q.add(six)); + assertFalse(q.add(six)); + } + + /** + * Add of non-Comparable throws CCE + */ + public void testAddNonComparable() { + NavigableSet q = set0(); + try { + q.add(new Object()); + q.add(new Object()); + shouldThrow(); + } catch (ClassCastException success) {} + } + + /** + * addAll(null) throws NPE + */ + public void testAddAll1() { + NavigableSet q = set0(); + try { + q.addAll(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll of a collection with null elements throws NPE + */ + public void testAddAll2() { + NavigableSet q = set0(); + Integer[] ints = new Integer[SIZE]; + try { + q.addAll(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll of a collection with any null elements throws NPE after + * possibly adding some elements + */ + public void testAddAll3() { + NavigableSet q = set0(); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i + SIZE); + try { + q.addAll(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Set contains all elements of successful addAll + */ + public void testAddAll5() { + Integer[] empty = new Integer[0]; + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(SIZE - 1 - i); + NavigableSet q = set0(); + assertFalse(q.addAll(Arrays.asList(empty))); + assertTrue(q.addAll(Arrays.asList(ints))); + for (int i = 0; i < SIZE; ++i) + assertEquals(new Integer(i), q.pollFirst()); + } + + /** + * poll succeeds unless empty + */ + public void testPoll() { + NavigableSet q = populatedSet(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.pollFirst()); + } + assertNull(q.pollFirst()); + } + + /** + * remove(x) removes x and returns true if present + */ + public void testRemoveElement() { + NavigableSet q = populatedSet(SIZE); + for (int i = 1; i < SIZE; i += 2) { + assertTrue(q.contains(i)); + assertTrue(q.remove(i)); + assertFalse(q.contains(i)); + assertTrue(q.contains(i - 1)); + } + for (int i = 0; i < SIZE; i += 2) { + assertTrue(q.contains(i)); + assertTrue(q.remove(i)); + assertFalse(q.contains(i)); + assertFalse(q.remove(i + 1)); + assertFalse(q.contains(i + 1)); + } + assertTrue(q.isEmpty()); + } + + /** + * contains(x) reports true when elements added but not yet removed + */ + public void testContains() { + NavigableSet q = populatedSet(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.contains(new Integer(i))); + q.pollFirst(); + assertFalse(q.contains(new Integer(i))); + } + } + + /** + * clear removes all elements + */ + public void testClear() { + NavigableSet q = populatedSet(SIZE); + q.clear(); + assertTrue(q.isEmpty()); + assertEquals(0, q.size()); + assertTrue(q.add(new Integer(1))); + assertFalse(q.isEmpty()); + q.clear(); + assertTrue(q.isEmpty()); + } + + /** + * containsAll(c) is true when c contains a subset of elements + */ + public void testContainsAll() { + NavigableSet q = populatedSet(SIZE); + NavigableSet p = set0(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.containsAll(p)); + assertFalse(p.containsAll(q)); + p.add(new Integer(i)); + } + assertTrue(p.containsAll(q)); + } + + /** + * retainAll(c) retains only those elements of c and reports true if changed + */ + public void testRetainAll() { + NavigableSet q = populatedSet(SIZE); + NavigableSet p = populatedSet(SIZE); + for (int i = 0; i < SIZE; ++i) { + boolean changed = q.retainAll(p); + if (i == 0) + assertFalse(changed); + else + assertTrue(changed); + + assertTrue(q.containsAll(p)); + assertEquals(SIZE - i, q.size()); + p.pollFirst(); + } + } + + /** + * removeAll(c) removes only those elements of c and reports true if changed + */ + public void testRemoveAll() { + for (int i = 1; i < SIZE; ++i) { + NavigableSet q = populatedSet(SIZE); + NavigableSet p = populatedSet(i); + assertTrue(q.removeAll(p)); + assertEquals(SIZE - i, q.size()); + for (int j = 0; j < i; ++j) { + Integer x = (Integer)(p.pollFirst()); + assertFalse(q.contains(x)); + } + } + } + + /** + * lower returns preceding element + */ + public void testLower() { + NavigableSet q = set5(); + Object e1 = q.lower(three); + assertEquals(two, e1); + + Object e2 = q.lower(six); + assertEquals(five, e2); + + Object e3 = q.lower(one); + assertNull(e3); + + Object e4 = q.lower(zero); + assertNull(e4); + } + + /** + * higher returns next element + */ + public void testHigher() { + NavigableSet q = set5(); + Object e1 = q.higher(three); + assertEquals(four, e1); + + Object e2 = q.higher(zero); + assertEquals(one, e2); + + Object e3 = q.higher(five); + assertNull(e3); + + Object e4 = q.higher(six); + assertNull(e4); + } + + /** + * floor returns preceding element + */ + public void testFloor() { + NavigableSet q = set5(); + Object e1 = q.floor(three); + assertEquals(three, e1); + + Object e2 = q.floor(six); + assertEquals(five, e2); + + Object e3 = q.floor(one); + assertEquals(one, e3); + + Object e4 = q.floor(zero); + assertNull(e4); + } + + /** + * ceiling returns next element + */ + public void testCeiling() { + NavigableSet q = set5(); + Object e1 = q.ceiling(three); + assertEquals(three, e1); + + Object e2 = q.ceiling(zero); + assertEquals(one, e2); + + Object e3 = q.ceiling(five); + assertEquals(five, e3); + + Object e4 = q.ceiling(six); + assertNull(e4); + } + + /** + * toArray contains all elements in sorted order + */ + public void testToArray() { + NavigableSet q = populatedSet(SIZE); + Object[] o = q.toArray(); + for (int i = 0; i < o.length; i++) + assertSame(o[i], q.pollFirst()); + } + + /** + * toArray(a) contains all elements in sorted order + */ + public void testToArray2() { + NavigableSet q = populatedSet(SIZE); + Integer[] ints = new Integer[SIZE]; + Integer[] array = q.toArray(ints); + assertSame(ints, array); + for (int i = 0; i < ints.length; i++) + assertSame(ints[i], q.pollFirst()); + } + + /** + * iterator iterates through all elements + */ + public void testIterator() { + NavigableSet q = populatedSet(SIZE); + Iterator it = q.iterator(); + int i; + for (i = 0; it.hasNext(); i++) + assertTrue(q.contains(it.next())); + assertEquals(i, SIZE); + assertIteratorExhausted(it); + } + + /** + * iterator of empty set has no elements + */ + public void testEmptyIterator() { + assertIteratorExhausted(set0().iterator()); + } + + /** + * iterator.remove removes current element + */ + public void testIteratorRemove() { + final NavigableSet q = set0(); + q.add(new Integer(2)); + q.add(new Integer(1)); + q.add(new Integer(3)); + + Iterator it = q.iterator(); + it.next(); + it.remove(); + + it = q.iterator(); + assertEquals(2, it.next()); + assertEquals(3, it.next()); + assertFalse(it.hasNext()); + } + + /** + * toString contains toStrings of elements + */ + public void testToString() { + NavigableSet q = populatedSet(SIZE); + String s = q.toString(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(s.contains(String.valueOf(i))); + } + } + + /** + * A deserialized serialized set has same elements + */ + public void testSerialization() throws Exception { + NavigableSet x = populatedSet(SIZE); + NavigableSet y = serialClone(x); + + assertNotSame(x, y); + assertEquals(x.size(), y.size()); + assertEquals(x, y); + assertEquals(y, x); + while (!x.isEmpty()) { + assertFalse(y.isEmpty()); + assertEquals(x.pollFirst(), y.pollFirst()); + } + assertTrue(y.isEmpty()); + } + + /** + * subSet returns set with keys in requested range + */ + public void testSubSetContents() { + NavigableSet set = set5(); + SortedSet sm = set.subSet(two, four); + assertEquals(two, sm.first()); + assertEquals(three, sm.last()); + assertEquals(2, sm.size()); + assertFalse(sm.contains(one)); + assertTrue(sm.contains(two)); + assertTrue(sm.contains(three)); + assertFalse(sm.contains(four)); + assertFalse(sm.contains(five)); + Iterator i = sm.iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(two, k); + k = (Integer)(i.next()); + assertEquals(three, k); + assertFalse(i.hasNext()); + Iterator j = sm.iterator(); + j.next(); + j.remove(); + assertFalse(set.contains(two)); + assertEquals(4, set.size()); + assertEquals(1, sm.size()); + assertEquals(three, sm.first()); + assertEquals(three, sm.last()); + assertTrue(sm.remove(three)); + assertTrue(sm.isEmpty()); + assertEquals(3, set.size()); + } + + public void testSubSetContents2() { + NavigableSet set = set5(); + SortedSet sm = set.subSet(two, three); + assertEquals(1, sm.size()); + assertEquals(two, sm.first()); + assertEquals(two, sm.last()); + assertFalse(sm.contains(one)); + assertTrue(sm.contains(two)); + assertFalse(sm.contains(three)); + assertFalse(sm.contains(four)); + assertFalse(sm.contains(five)); + Iterator i = sm.iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(two, k); + assertFalse(i.hasNext()); + Iterator j = sm.iterator(); + j.next(); + j.remove(); + assertFalse(set.contains(two)); + assertEquals(4, set.size()); + assertEquals(0, sm.size()); + assertTrue(sm.isEmpty()); + assertFalse(sm.remove(three)); + assertEquals(4, set.size()); + } + + /** + * headSet returns set with keys in requested range + */ + public void testHeadSetContents() { + NavigableSet set = set5(); + SortedSet sm = set.headSet(four); + assertTrue(sm.contains(one)); + assertTrue(sm.contains(two)); + assertTrue(sm.contains(three)); + assertFalse(sm.contains(four)); + assertFalse(sm.contains(five)); + Iterator i = sm.iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(one, k); + k = (Integer)(i.next()); + assertEquals(two, k); + k = (Integer)(i.next()); + assertEquals(three, k); + assertFalse(i.hasNext()); + sm.clear(); + assertTrue(sm.isEmpty()); + assertEquals(2, set.size()); + assertEquals(four, set.first()); + } + + /** + * tailSet returns set with keys in requested range + */ + public void testTailSetContents() { + NavigableSet set = set5(); + SortedSet sm = set.tailSet(two); + assertFalse(sm.contains(one)); + assertTrue(sm.contains(two)); + assertTrue(sm.contains(three)); + assertTrue(sm.contains(four)); + assertTrue(sm.contains(five)); + Iterator i = sm.iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(two, k); + k = (Integer)(i.next()); + assertEquals(three, k); + k = (Integer)(i.next()); + assertEquals(four, k); + k = (Integer)(i.next()); + assertEquals(five, k); + assertFalse(i.hasNext()); + + SortedSet ssm = sm.tailSet(four); + assertEquals(four, ssm.first()); + assertEquals(five, ssm.last()); + assertTrue(ssm.remove(four)); + assertEquals(1, ssm.size()); + assertEquals(3, sm.size()); + assertEquals(4, set.size()); + } + + /** + * size changes when elements added and removed + */ + public void testDescendingSize() { + NavigableSet q = populatedSet(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(SIZE - i, q.size()); + q.pollFirst(); + } + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.size()); + q.add(new Integer(i)); + } + } + + /** + * Add of comparable element succeeds + */ + public void testDescendingAdd() { + NavigableSet q = dset0(); + assertTrue(q.add(m6)); + } + + /** + * Add of duplicate element fails + */ + public void testDescendingAddDup() { + NavigableSet q = dset0(); + assertTrue(q.add(m6)); + assertFalse(q.add(m6)); + } + + /** + * Add of non-Comparable throws CCE + */ + public void testDescendingAddNonComparable() { + NavigableSet q = dset0(); + try { + q.add(new Object()); + q.add(new Object()); + shouldThrow(); + } catch (ClassCastException success) {} + } + + /** + * addAll(null) throws NPE + */ + public void testDescendingAddAll1() { + NavigableSet q = dset0(); + try { + q.addAll(null); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll of a collection with null elements throws NPE + */ + public void testDescendingAddAll2() { + NavigableSet q = dset0(); + Integer[] ints = new Integer[SIZE]; + try { + q.addAll(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * addAll of a collection with any null elements throws NPE after + * possibly adding some elements + */ + public void testDescendingAddAll3() { + NavigableSet q = dset0(); + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) + ints[i] = new Integer(i + SIZE); + try { + q.addAll(Arrays.asList(ints)); + shouldThrow(); + } catch (NullPointerException success) {} + } + + /** + * Set contains all elements of successful addAll + */ + public void testDescendingAddAll5() { + Integer[] empty = new Integer[0]; + Integer[] ints = new Integer[SIZE]; + for (int i = 0; i < SIZE; ++i) + ints[i] = new Integer(SIZE - 1 - i); + NavigableSet q = dset0(); + assertFalse(q.addAll(Arrays.asList(empty))); + assertTrue(q.addAll(Arrays.asList(ints))); + for (int i = 0; i < SIZE; ++i) + assertEquals(new Integer(i), q.pollFirst()); + } + + /** + * poll succeeds unless empty + */ + public void testDescendingPoll() { + NavigableSet q = populatedSet(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertEquals(i, q.pollFirst()); + } + assertNull(q.pollFirst()); + } + + /** + * remove(x) removes x and returns true if present + */ + public void testDescendingRemoveElement() { + NavigableSet q = populatedSet(SIZE); + for (int i = 1; i < SIZE; i += 2) { + assertTrue(q.remove(new Integer(i))); + } + for (int i = 0; i < SIZE; i += 2) { + assertTrue(q.remove(new Integer(i))); + assertFalse(q.remove(new Integer(i + 1))); + } + assertTrue(q.isEmpty()); + } + + /** + * contains(x) reports true when elements added but not yet removed + */ + public void testDescendingContains() { + NavigableSet q = populatedSet(SIZE); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.contains(new Integer(i))); + q.pollFirst(); + assertFalse(q.contains(new Integer(i))); + } + } + + /** + * clear removes all elements + */ + public void testDescendingClear() { + NavigableSet q = populatedSet(SIZE); + q.clear(); + assertTrue(q.isEmpty()); + assertEquals(0, q.size()); + assertTrue(q.add(new Integer(1))); + assertFalse(q.isEmpty()); + q.clear(); + assertTrue(q.isEmpty()); + } + + /** + * containsAll(c) is true when c contains a subset of elements + */ + public void testDescendingContainsAll() { + NavigableSet q = populatedSet(SIZE); + NavigableSet p = dset0(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(q.containsAll(p)); + assertFalse(p.containsAll(q)); + p.add(new Integer(i)); + } + assertTrue(p.containsAll(q)); + } + + /** + * retainAll(c) retains only those elements of c and reports true if changed + */ + public void testDescendingRetainAll() { + NavigableSet q = populatedSet(SIZE); + NavigableSet p = populatedSet(SIZE); + for (int i = 0; i < SIZE; ++i) { + boolean changed = q.retainAll(p); + if (i == 0) + assertFalse(changed); + else + assertTrue(changed); + + assertTrue(q.containsAll(p)); + assertEquals(SIZE - i, q.size()); + p.pollFirst(); + } + } + + /** + * removeAll(c) removes only those elements of c and reports true if changed + */ + public void testDescendingRemoveAll() { + for (int i = 1; i < SIZE; ++i) { + NavigableSet q = populatedSet(SIZE); + NavigableSet p = populatedSet(i); + assertTrue(q.removeAll(p)); + assertEquals(SIZE - i, q.size()); + for (int j = 0; j < i; ++j) { + Integer x = (Integer)(p.pollFirst()); + assertFalse(q.contains(x)); + } + } + } + + /** + * lower returns preceding element + */ + public void testDescendingLower() { + NavigableSet q = dset5(); + Object e1 = q.lower(m3); + assertEquals(m2, e1); + + Object e2 = q.lower(m6); + assertEquals(m5, e2); + + Object e3 = q.lower(m1); + assertNull(e3); + + Object e4 = q.lower(zero); + assertNull(e4); + } + + /** + * higher returns next element + */ + public void testDescendingHigher() { + NavigableSet q = dset5(); + Object e1 = q.higher(m3); + assertEquals(m4, e1); + + Object e2 = q.higher(zero); + assertEquals(m1, e2); + + Object e3 = q.higher(m5); + assertNull(e3); + + Object e4 = q.higher(m6); + assertNull(e4); + } + + /** + * floor returns preceding element + */ + public void testDescendingFloor() { + NavigableSet q = dset5(); + Object e1 = q.floor(m3); + assertEquals(m3, e1); + + Object e2 = q.floor(m6); + assertEquals(m5, e2); + + Object e3 = q.floor(m1); + assertEquals(m1, e3); + + Object e4 = q.floor(zero); + assertNull(e4); + } + + /** + * ceiling returns next element + */ + public void testDescendingCeiling() { + NavigableSet q = dset5(); + Object e1 = q.ceiling(m3); + assertEquals(m3, e1); + + Object e2 = q.ceiling(zero); + assertEquals(m1, e2); + + Object e3 = q.ceiling(m5); + assertEquals(m5, e3); + + Object e4 = q.ceiling(m6); + assertNull(e4); + } + + /** + * toArray contains all elements + */ + public void testDescendingToArray() { + NavigableSet q = populatedSet(SIZE); + Object[] o = q.toArray(); + Arrays.sort(o); + for (int i = 0; i < o.length; i++) + assertEquals(o[i], q.pollFirst()); + } + + /** + * toArray(a) contains all elements + */ + public void testDescendingToArray2() { + NavigableSet q = populatedSet(SIZE); + Integer[] ints = new Integer[SIZE]; + assertSame(ints, q.toArray(ints)); + Arrays.sort(ints); + for (int i = 0; i < ints.length; i++) + assertEquals(ints[i], q.pollFirst()); + } + + /** + * iterator iterates through all elements + */ + public void testDescendingIterator() { + NavigableSet q = populatedSet(SIZE); + int i = 0; + Iterator it = q.iterator(); + while (it.hasNext()) { + assertTrue(q.contains(it.next())); + ++i; + } + assertEquals(i, SIZE); + } + + /** + * iterator of empty set has no elements + */ + public void testDescendingEmptyIterator() { + NavigableSet q = dset0(); + int i = 0; + Iterator it = q.iterator(); + while (it.hasNext()) { + assertTrue(q.contains(it.next())); + ++i; + } + assertEquals(0, i); + } + + /** + * iterator.remove removes current element + */ + public void testDescendingIteratorRemove() { + final NavigableSet q = dset0(); + q.add(new Integer(2)); + q.add(new Integer(1)); + q.add(new Integer(3)); + + Iterator it = q.iterator(); + it.next(); + it.remove(); + + it = q.iterator(); + assertEquals(2, it.next()); + assertEquals(3, it.next()); + assertFalse(it.hasNext()); + } + + /** + * toString contains toStrings of elements + */ + public void testDescendingToString() { + NavigableSet q = populatedSet(SIZE); + String s = q.toString(); + for (int i = 0; i < SIZE; ++i) { + assertTrue(s.contains(String.valueOf(i))); + } + } + + /** + * A deserialized serialized set has same elements + */ + public void testDescendingSerialization() throws Exception { + NavigableSet x = dset5(); + NavigableSet y = serialClone(x); + + assertNotSame(x, y); + assertEquals(x.size(), y.size()); + assertEquals(x.toString(), y.toString()); + assertEquals(x, y); + assertEquals(y, x); + while (!x.isEmpty()) { + assertFalse(y.isEmpty()); + assertEquals(x.pollFirst(), y.pollFirst()); + } + assertTrue(y.isEmpty()); + } + + /** + * subSet returns set with keys in requested range + */ + public void testDescendingSubSetContents() { + NavigableSet set = dset5(); + SortedSet sm = set.subSet(m2, m4); + assertEquals(m2, sm.first()); + assertEquals(m3, sm.last()); + assertEquals(2, sm.size()); + assertFalse(sm.contains(m1)); + assertTrue(sm.contains(m2)); + assertTrue(sm.contains(m3)); + assertFalse(sm.contains(m4)); + assertFalse(sm.contains(m5)); + Iterator i = sm.iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(m2, k); + k = (Integer)(i.next()); + assertEquals(m3, k); + assertFalse(i.hasNext()); + Iterator j = sm.iterator(); + j.next(); + j.remove(); + assertFalse(set.contains(m2)); + assertEquals(4, set.size()); + assertEquals(1, sm.size()); + assertEquals(m3, sm.first()); + assertEquals(m3, sm.last()); + assertTrue(sm.remove(m3)); + assertTrue(sm.isEmpty()); + assertEquals(3, set.size()); + } + + public void testDescendingSubSetContents2() { + NavigableSet set = dset5(); + SortedSet sm = set.subSet(m2, m3); + assertEquals(1, sm.size()); + assertEquals(m2, sm.first()); + assertEquals(m2, sm.last()); + assertFalse(sm.contains(m1)); + assertTrue(sm.contains(m2)); + assertFalse(sm.contains(m3)); + assertFalse(sm.contains(m4)); + assertFalse(sm.contains(m5)); + Iterator i = sm.iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(m2, k); + assertFalse(i.hasNext()); + Iterator j = sm.iterator(); + j.next(); + j.remove(); + assertFalse(set.contains(m2)); + assertEquals(4, set.size()); + assertEquals(0, sm.size()); + assertTrue(sm.isEmpty()); + assertFalse(sm.remove(m3)); + assertEquals(4, set.size()); + } + + /** + * headSet returns set with keys in requested range + */ + public void testDescendingHeadSetContents() { + NavigableSet set = dset5(); + SortedSet sm = set.headSet(m4); + assertTrue(sm.contains(m1)); + assertTrue(sm.contains(m2)); + assertTrue(sm.contains(m3)); + assertFalse(sm.contains(m4)); + assertFalse(sm.contains(m5)); + Iterator i = sm.iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(m1, k); + k = (Integer)(i.next()); + assertEquals(m2, k); + k = (Integer)(i.next()); + assertEquals(m3, k); + assertFalse(i.hasNext()); + sm.clear(); + assertTrue(sm.isEmpty()); + assertEquals(2, set.size()); + assertEquals(m4, set.first()); + } + + /** + * tailSet returns set with keys in requested range + */ + public void testDescendingTailSetContents() { + NavigableSet set = dset5(); + SortedSet sm = set.tailSet(m2); + assertFalse(sm.contains(m1)); + assertTrue(sm.contains(m2)); + assertTrue(sm.contains(m3)); + assertTrue(sm.contains(m4)); + assertTrue(sm.contains(m5)); + Iterator i = sm.iterator(); + Object k; + k = (Integer)(i.next()); + assertEquals(m2, k); + k = (Integer)(i.next()); + assertEquals(m3, k); + k = (Integer)(i.next()); + assertEquals(m4, k); + k = (Integer)(i.next()); + assertEquals(m5, k); + assertFalse(i.hasNext()); + + SortedSet ssm = sm.tailSet(m4); + assertEquals(m4, ssm.first()); + assertEquals(m5, ssm.last()); + assertTrue(ssm.remove(m4)); + assertEquals(1, ssm.size()); + assertEquals(3, sm.size()); + assertEquals(4, set.size()); + } + + /** + * addAll is idempotent + */ + public void testAddAll_idempotent() throws Exception { + Set x = populatedSet(SIZE); + Set y = new TreeSet(x); + y.addAll(x); + assertEquals(x, y); + assertEquals(y, x); + } + +} diff --git a/jdk/test/java/util/stream/bootlib/java.base/java/util/stream/DoubleStreamTestDataProvider.java b/jdk/test/java/util/stream/bootlib/java.base/java/util/stream/DoubleStreamTestDataProvider.java index 0eb7c874249..d9a87bb8115 100644 --- a/jdk/test/java/util/stream/bootlib/java.base/java/util/stream/DoubleStreamTestDataProvider.java +++ b/jdk/test/java/util/stream/bootlib/java.base/java/util/stream/DoubleStreamTestDataProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -41,6 +41,7 @@ public class DoubleStreamTestDataProvider { private static final double[] pseudoRandom; private static final Object[][] testData; + private static final Object[][] testSmallData; private static final Object[][] spliteratorTestData; static { @@ -78,11 +79,15 @@ public class DoubleStreamTestDataProvider { static { { - List list = new ArrayList<>(); + List listSmall = new ArrayList<>(); + List list1000 = new ArrayList<>(); + List list = null; for (Object[] data : arrays) { final Object name = data[0]; final double[] doubles = (double[]) data[1]; + list = doubles.length >= 1000 ? list1000 : listSmall; + list.add(new Object[]{"array:" + name, TestData.Factory.ofArray("array:" + name, doubles)}); @@ -93,7 +98,9 @@ public class DoubleStreamTestDataProvider { list.add(new Object[]{"SpinedList:" + name, TestData.Factory.ofSpinedBuffer("SpinedList:" + name, isl)}); } - testData = list.toArray(new Object[0][]); + testSmallData = listSmall.toArray(new Object[0][]); + list1000.addAll(listSmall); + testData = list1000.toArray(new Object[0][]); } { @@ -136,6 +143,11 @@ public class DoubleStreamTestDataProvider { return testData; } + @DataProvider(name = "DoubleStreamTestData.small") + public static Object[][] makeSmallDoubleStreamTestData() { + return testSmallData; + } + // returns an array of (String name, Supplier>) @DataProvider(name = "DoubleSpliterator") public static Object[][] spliteratorProvider() { diff --git a/jdk/test/java/util/stream/bootlib/java.base/java/util/stream/IntStreamTestDataProvider.java b/jdk/test/java/util/stream/bootlib/java.base/java/util/stream/IntStreamTestDataProvider.java index dded670d167..945cd9580e5 100644 --- a/jdk/test/java/util/stream/bootlib/java.base/java/util/stream/IntStreamTestDataProvider.java +++ b/jdk/test/java/util/stream/bootlib/java.base/java/util/stream/IntStreamTestDataProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -41,6 +41,7 @@ public class IntStreamTestDataProvider { private static final int[] pseudoRandom; private static final Object[][] testData; + private static final Object[][] testSmallData; private static final Object[][] spliteratorTestData; static { @@ -78,11 +79,15 @@ public class IntStreamTestDataProvider { static { { - List list = new ArrayList<>(); + List listSmall = new ArrayList<>(); + List list1000 = new ArrayList<>(); + List list = null; for (Object[] data : arrays) { final Object name = data[0]; final int[] ints = (int[]) data[1]; + list = ints.length >= 1000 ? list1000 : listSmall; + list.add(new Object[]{"array:" + name, TestData.Factory.ofArray("array:" + name, ints)}); @@ -98,7 +103,9 @@ public class IntStreamTestDataProvider { list.add(streamDataDescr("IntStream.rangeClosed(0,l): " + ints.length, () -> IntStream.rangeClosed(0, ints.length))); } - testData = list.toArray(new Object[0][]); + testSmallData = listSmall.toArray(new Object[0][]); + list1000.addAll(listSmall); + testData = list1000.toArray(new Object[0][]); } { @@ -150,6 +157,11 @@ public class IntStreamTestDataProvider { return testData; } + @DataProvider(name = "IntStreamTestData.small") + public static Object[][] makeSmallIntStreamTestData() { + return testSmallData; + } + // returns an array of (String name, Supplier>) @DataProvider(name = "IntSpliterator") public static Object[][] spliteratorProvider() { diff --git a/jdk/test/java/util/stream/bootlib/java.base/java/util/stream/LongStreamTestDataProvider.java b/jdk/test/java/util/stream/bootlib/java.base/java/util/stream/LongStreamTestDataProvider.java index 4ce7ae6d217..80b1dcc1019 100644 --- a/jdk/test/java/util/stream/bootlib/java.base/java/util/stream/LongStreamTestDataProvider.java +++ b/jdk/test/java/util/stream/bootlib/java.base/java/util/stream/LongStreamTestDataProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -41,6 +41,7 @@ public class LongStreamTestDataProvider { private static final long[] pseudoRandom; private static final Object[][] testData; + private static final Object[][] testSmallData; private static final Object[][] spliteratorTestData; static { @@ -78,11 +79,15 @@ public class LongStreamTestDataProvider { static { { - List list = new ArrayList<>(); + List listSmall = new ArrayList<>(); + List list1000 = new ArrayList<>(); + List list = null; for (Object[] data : arrays) { final Object name = data[0]; final long[] longs = (long[]) data[1]; + list = longs.length >= 1000 ? list1000 : listSmall; + list.add(new Object[]{"array:" + name, TestData.Factory.ofArray("array:" + name, longs)}); @@ -98,7 +103,9 @@ public class LongStreamTestDataProvider { list.add(streamDataDescr("LongStream.longRangeClosed(0,l): " + longs.length, () -> LongStream.rangeClosed(0, longs.length))); } - testData = list.toArray(new Object[0][]); + testSmallData = listSmall.toArray(new Object[0][]); + list1000.addAll(listSmall); + testData = list1000.toArray(new Object[0][]); } { @@ -150,6 +157,11 @@ public class LongStreamTestDataProvider { return testData; } + @DataProvider(name = "LongStreamTestData.small") + public static Object[][] makeSmallLongStreamTestData() { + return testSmallData; + } + // returns an array of (String name, Supplier>) @DataProvider(name = "LongSpliterator") public static Object[][] spliteratorProvider() { diff --git a/jdk/test/java/util/stream/bootlib/java.base/java/util/stream/StreamTestDataProvider.java b/jdk/test/java/util/stream/bootlib/java.base/java/util/stream/StreamTestDataProvider.java index 6f772f391ee..8a849015260 100644 --- a/jdk/test/java/util/stream/bootlib/java.base/java/util/stream/StreamTestDataProvider.java +++ b/jdk/test/java/util/stream/bootlib/java.base/java/util/stream/StreamTestDataProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -46,6 +46,7 @@ public class StreamTestDataProvider { private static final Integer[] pseudoRandom; private static final Object[][] testData; + private static final Object[][] testSmallData; private static final Object[][] withNullTestData; private static final Object[][] spliteratorTestData; @@ -84,12 +85,16 @@ public class StreamTestDataProvider { static { { - List list = new ArrayList<>(); + List listSmall = new ArrayList<>(); + List list1000 = new ArrayList<>(); + List list = null; for (Object[] data : arrays) { final Object name = data[0]; final Integer[] ints = (Integer[])data[1]; final List intsAsList = Arrays.asList(ints); + list = ints.length >= 1000 ? list1000 : listSmall; + list.add(arrayDataDescr("array:" + name, ints)); list.add(collectionDataDescr("ArrayList.asList:" + name, intsAsList)); list.add(collectionDataDescr("ArrayList:" + name, new ArrayList<>(intsAsList))); @@ -114,7 +119,9 @@ public class StreamTestDataProvider { // @@@ Add more } - testData = list.toArray(new Object[0][]); + testSmallData = listSmall.toArray(new Object[0][]); + list1000.addAll(listSmall); + testData = list1000.toArray(new Object[0][]); } // Simple combination of numbers and null values, probably excessive but may catch @@ -192,6 +199,11 @@ public class StreamTestDataProvider { return testData; } + @DataProvider(name = "StreamTestData.small") + public static Object[][] makeSmallStreamTestData() { + return testSmallData; + } + @DataProvider(name = "withNull:StreamTestData") public static Object[][] makeStreamWithNullTestData() { return withNullTestData; diff --git a/jdk/test/java/util/stream/test/org/openjdk/tests/java/util/stream/FlatMapOpTest.java b/jdk/test/java/util/stream/test/org/openjdk/tests/java/util/stream/FlatMapOpTest.java index 453ec6e952c..afbedc871da 100644 --- a/jdk/test/java/util/stream/test/org/openjdk/tests/java/util/stream/FlatMapOpTest.java +++ b/jdk/test/java/util/stream/test/org/openjdk/tests/java/util/stream/FlatMapOpTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,7 +24,7 @@ /* * @test * @summary flat-map operations - * @bug 8044047 + * @bug 8044047 8076458 */ package org.openjdk.tests.java.util.stream; @@ -140,7 +140,10 @@ public class FlatMapOpTest extends OpTestCase { result = exerciseOps(data, s-> s.flatMap(e -> Stream.empty())); assertEquals(0, result.size()); + } + @Test(dataProvider = "StreamTestData.small", dataProviderClass = StreamTestDataProvider.class) + public void testOpsX(String name, TestData.OfRef data) { exerciseOps(data, s -> s.flatMap(mfLt)); exerciseOps(data, s -> s.flatMap(integerRangeMapper)); exerciseOps(data, s -> s.flatMap((Integer e) -> IntStream.range(0, e).boxed().limit(10))); @@ -156,7 +159,10 @@ public class FlatMapOpTest extends OpTestCase { result = exerciseOps(data, s -> s.flatMap(i -> IntStream.empty())); assertEquals(0, result.size()); + } + @Test(dataProvider = "IntStreamTestData.small", dataProviderClass = IntStreamTestDataProvider.class) + public void testIntOpsX(String name, TestData.OfInt data) { exerciseOps(data, s -> s.flatMap(e -> IntStream.range(0, e))); exerciseOps(data, s -> s.flatMap(e -> IntStream.range(0, e).limit(10))); } @@ -171,7 +177,10 @@ public class FlatMapOpTest extends OpTestCase { result = exerciseOps(data, s -> LongStream.empty()); assertEquals(0, result.size()); + } + @Test(dataProvider = "LongStreamTestData.small", dataProviderClass = LongStreamTestDataProvider.class) + public void testLongOpsX(String name, TestData.OfLong data) { exerciseOps(data, s -> s.flatMap(e -> LongStream.range(0, e))); exerciseOps(data, s -> s.flatMap(e -> LongStream.range(0, e).limit(10))); } @@ -186,7 +195,10 @@ public class FlatMapOpTest extends OpTestCase { result = exerciseOps(data, s -> DoubleStream.empty()); assertEquals(0, result.size()); + } + @Test(dataProvider = "DoubleStreamTestData.small", dataProviderClass = DoubleStreamTestDataProvider.class) + public void testDoubleOpsX(String name, TestData.OfDouble data) { exerciseOps(data, s -> s.flatMap(e -> IntStream.range(0, (int) e).asDoubleStream())); exerciseOps(data, s -> s.flatMap(e -> IntStream.range(0, (int) e).limit(10).asDoubleStream())); } diff --git a/jdk/test/javax/net/ssl/templates/SSLSocketSSLEngineTemplate.java b/jdk/test/javax/net/ssl/templates/SSLSocketSSLEngineTemplate.java index 274f9d8e255..d7f0d38cdee 100644 --- a/jdk/test/javax/net/ssl/templates/SSLSocketSSLEngineTemplate.java +++ b/jdk/test/javax/net/ssl/templates/SSLSocketSSLEngineTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,6 +31,7 @@ * @bug 7105780 * @summary Add SSLSocket client/SSLEngine server to templates directory. * @run main/othervm SSLSocketSSLEngineTemplate + * @key intermittent */ /** diff --git a/jdk/test/lib/testlibrary/java/util/jar/CreateMultiReleaseTestJars.java b/jdk/test/lib/testlibrary/java/util/jar/CreateMultiReleaseTestJars.java index 299a9ca65db..390c4fa0c69 100644 --- a/jdk/test/lib/testlibrary/java/util/jar/CreateMultiReleaseTestJars.java +++ b/jdk/test/lib/testlibrary/java/util/jar/CreateMultiReleaseTestJars.java @@ -21,12 +21,21 @@ * questions. */ +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.CertPath; +import java.security.cert.CertificateFactory; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.zip.ZipFile; +import jdk.security.jarsigner.JarSigner; public class CreateMultiReleaseTestJars { final private String main = @@ -120,14 +129,21 @@ public class CreateMultiReleaseTestJars { String testsrc = System.getProperty("test.src","."); String testdir = findTestDir(testsrc); String keystore = testdir + "/sun/security/tools/jarsigner/JarSigning.keystore"; - String[] jsArgs = { - "-keystore", keystore, - "-storepass", "bbbbbb", - "-signedJar", "signed-multi-release.jar", - "multi-release.jar", "b" - }; - sun.security.tools.jarsigner.Main.main(jsArgs); + // jarsigner -keystore keystore -storepass "bbbbbb" + // -signedJar signed-multi-release.jar multi-release.jar b + + char[] password = "bbbbbb".toCharArray(); + KeyStore ks = KeyStore.getInstance(new File(keystore), password); + PrivateKey pkb = (PrivateKey)ks.getKey("b", password); + CertPath cp = CertificateFactory.getInstance("X.509") + .generateCertPath(Arrays.asList(ks.getCertificateChain("b"))); + JarSigner js = new JarSigner.Builder(pkb, cp).build(); + try (ZipFile in = new ZipFile("multi-release.jar"); + FileOutputStream os = new FileOutputStream("signed-multi-release.jar")) + { + js.sign(in, os); + } } String findTestDir(String dir) throws IOException { diff --git a/jdk/test/sun/nio/ch/TestMaxCachedBufferSize.java b/jdk/test/sun/nio/ch/TestMaxCachedBufferSize.java index 207485afe63..8c8049bdf5c 100644 --- a/jdk/test/sun/nio/ch/TestMaxCachedBufferSize.java +++ b/jdk/test/sun/nio/ch/TestMaxCachedBufferSize.java @@ -42,13 +42,13 @@ import java.util.Random; /* * @test + * @requires sun.arch.data.model == "64" * @build TestMaxCachedBufferSize * @run main/othervm TestMaxCachedBufferSize * @run main/othervm -Djdk.nio.maxCachedBufferSize=0 TestMaxCachedBufferSize * @run main/othervm -Djdk.nio.maxCachedBufferSize=2000 TestMaxCachedBufferSize * @run main/othervm -Djdk.nio.maxCachedBufferSize=100000 TestMaxCachedBufferSize * @run main/othervm -Djdk.nio.maxCachedBufferSize=10000000 TestMaxCachedBufferSize - * * @summary Test the implementation of the jdk.nio.maxCachedBufferSize property. */ public class TestMaxCachedBufferSize { diff --git a/langtools/.hgtags b/langtools/.hgtags index 3786fbae996..90a13fb7426 100644 --- a/langtools/.hgtags +++ b/langtools/.hgtags @@ -345,3 +345,4 @@ cb73b474703e2de266542b505cffd658bcc052da jdk-9+99 51136404ee5e6cd5868b60d66ebd55a02170b508 jdk-9+100 3b3bea483542bc08278af529fb25f2e5930da945 jdk-9+101 6149fc30cd710eb3484dc9863d8837ecaedb96b6 jdk-9+102 +94cfc50c1b8a74fd7b0ed2e9e4f4a9dab4f2c6a1 jdk-9+103 diff --git a/langtools/make/CompileInterim.gmk b/langtools/make/CompileInterim.gmk index 018343bb4ae..2ccd58feaa2 100644 --- a/langtools/make/CompileInterim.gmk +++ b/langtools/make/CompileInterim.gmk @@ -46,7 +46,7 @@ $(eval $(call SetupJavaCompilation,BUILD_INTERIM_LANGTOOLS, \ $(SUPPORT_OUTPUTDIR)/gensrc/jdk.compiler \ $(SUPPORT_OUTPUTDIR)/gensrc/jdk.javadoc \ $(SUPPORT_OUTPUTDIR)/gensrc/jdk.jdeps, \ - EXCLUDES := sun jdk, \ + EXCLUDES := sun, \ COPY := .gif .png .xml .css .js javax.tools.JavaCompilerTool, \ BIN := $(BUILDTOOLS_OUTPUTDIR)/langtools_interim_classes, \ JAR := $(INTERIM_LANGTOOLS_JAR))) diff --git a/langtools/make/gensrc/Gensrc-jdk.javadoc.gmk b/langtools/make/gensrc/Gensrc-jdk.javadoc.gmk index 1d9ab44b32b..3720b1be5ae 100644 --- a/langtools/make/gensrc/Gensrc-jdk.javadoc.gmk +++ b/langtools/make/gensrc/Gensrc-jdk.javadoc.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -25,9 +25,12 @@ include GensrcCommon.gmk -$(eval $(call SetupVersionProperties,JAVADOC_VERSION,\ +$(eval $(call SetupVersionProperties,OLD_JAVADOC_VERSION,\ com/sun/tools/javadoc/resources/version.properties)) +$(eval $(call SetupVersionProperties,JAVADOC_VERSION,\ + jdk/javadoc/internal/tool/resources/version.properties)) + $(eval $(call SetupCompileProperties,COMPILE_PROPERTIES, $(JAVADOC_VERSION))) all: $(COMPILE_PROPERTIES) diff --git a/langtools/make/netbeans/langtools/build.xml b/langtools/make/netbeans/langtools/build.xml index 9e6c4cfddc8..0013b62228e 100644 --- a/langtools/make/netbeans/langtools/build.xml +++ b/langtools/make/netbeans/langtools/build.xml @@ -93,7 +93,11 @@ - + + + diff --git a/langtools/src/java.compiler/share/classes/javax/tools/ToolProvider.java b/langtools/src/java.compiler/share/classes/javax/tools/ToolProvider.java index 85da537519f..950daf73328 100644 --- a/langtools/src/java.compiler/share/classes/javax/tools/ToolProvider.java +++ b/langtools/src/java.compiler/share/classes/javax/tools/ToolProvider.java @@ -104,7 +104,7 @@ public class ToolProvider { } private static final String systemDocumentationToolName - = "com.sun.tools.javadoc.api.JavadocTool"; + = "jdk.javadoc.internal.api.JavadocTool"; /** * Returns the Java™ programming language documentation tool provided diff --git a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java index 9456af32f9c..68e36be3f44 100644 --- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java +++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java @@ -26,9 +26,7 @@ package com.sun.tools.javac.code; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; import javax.lang.model.element.ElementVisitor; import javax.tools.JavaFileObject; @@ -39,7 +37,6 @@ import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.Completer; import com.sun.tools.javac.code.Symbol.CompletionFailure; import com.sun.tools.javac.code.Symbol.MethodSymbol; -import com.sun.tools.javac.code.Symbol.OperatorSymbol; import com.sun.tools.javac.code.Symbol.PackageSymbol; import com.sun.tools.javac.code.Symbol.TypeSymbol; import com.sun.tools.javac.code.Symbol.VarSymbol; @@ -50,7 +47,6 @@ import com.sun.tools.javac.code.Type.JCPrimitiveType; import com.sun.tools.javac.code.Type.JCVoidType; import com.sun.tools.javac.code.Type.MethodType; import com.sun.tools.javac.code.Type.UnknownType; -import com.sun.tools.javac.jvm.ByteCodes; import com.sun.tools.javac.jvm.Target; import com.sun.tools.javac.util.Assert; import com.sun.tools.javac.util.Context; @@ -65,7 +61,6 @@ import com.sun.tools.javac.util.Names; import static com.sun.tools.javac.code.Flags.*; import static com.sun.tools.javac.code.Kinds.Kind.*; -import static com.sun.tools.javac.jvm.ByteCodes.*; import static com.sun.tools.javac.code.TypeTag.*; /** A class that defines all predefined constants and operators @@ -193,6 +188,7 @@ public class Symtab { public final Type autoCloseableType; public final Type trustMeType; public final Type lambdaMetafactory; + public final Type stringConcatFactory; public final Type repeatableType; public final Type documentedType; public final Type elementTypeType; @@ -472,6 +468,7 @@ public class Symtab { trustMeType = enterClass("java.lang.SafeVarargs"); nativeHeaderType = enterClass("java.lang.annotation.Native"); lambdaMetafactory = enterClass("java.lang.invoke.LambdaMetafactory"); + stringConcatFactory = enterClass("java.lang.invoke.StringConcatFactory"); functionalInterfaceType = enterClass("java.lang.FunctionalInterface"); synthesizeEmptyInterfaceIfMissing(autoCloseableType); @@ -479,6 +476,7 @@ public class Symtab { synthesizeEmptyInterfaceIfMissing(serializableType); synthesizeEmptyInterfaceIfMissing(lambdaMetafactory); synthesizeEmptyInterfaceIfMissing(serializedLambdaType); + synthesizeEmptyInterfaceIfMissing(stringConcatFactory); synthesizeBoxTypeIfMissing(doubleType); synthesizeBoxTypeIfMissing(floatType); synthesizeBoxTypeIfMissing(voidType); diff --git a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ArgumentAttr.java b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ArgumentAttr.java index 35185e73afc..7ce6ccd17ec 100644 --- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ArgumentAttr.java +++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ArgumentAttr.java @@ -68,6 +68,7 @@ import java.util.Optional; import java.util.function.Function; import java.util.function.Supplier; +import static com.sun.tools.javac.code.TypeTag.ARRAY; import static com.sun.tools.javac.code.TypeTag.DEFERRED; import static com.sun.tools.javac.code.TypeTag.FORALL; import static com.sun.tools.javac.code.TypeTag.METHOD; @@ -275,7 +276,7 @@ public class ArgumentAttr extends JCTree.Visitor { res.type != null && res.type.hasTag(FORALL) || (res.flags() & Flags.VARARGS) != 0 || (TreeInfo.isStaticSelector(exprTree, tree.name.table.names) && - exprTree.type.isRaw())) { + exprTree.type.isRaw() && !exprTree.type.hasTag(ARRAY))) { tree.overloadKind = JCMemberReference.OverloadKind.OVERLOADED; } else { tree.overloadKind = JCMemberReference.OverloadKind.UNOVERLOADED; diff --git a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java index d5bc1cdedef..f62d866bacb 100644 --- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java +++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java @@ -239,7 +239,7 @@ public class Attr extends JCTree.Visitor { //this means we are dealing with a partially inferred poly expression owntype = shouldCheck ? resultInfo.pt : found; if (resultInfo.checkMode.installPostInferenceHook()) { - inferenceContext.addFreeTypeListener(List.of(found, resultInfo.pt), + inferenceContext.addFreeTypeListener(List.of(found), instantiatedContext -> { ResultInfo pendingResult = resultInfo.dup(inferenceContext.asInstType(resultInfo.pt)); diff --git a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java index 583a34bf8df..73533e7ab43 100644 --- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java +++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java @@ -1812,7 +1812,7 @@ public class LambdaToMethod extends TreeTranslator { TranslationContext(T tree) { this.tree = tree; - this.owner = owner(); + this.owner = owner(true); this.depth = frameStack.size() - 1; this.prev = context(); ClassSymbol csym = diff --git a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java index f7dd6da221d..83ed8adbef5 100644 --- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java +++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java @@ -25,8 +25,6 @@ package com.sun.tools.javac.jvm; -import java.util.*; - import com.sun.tools.javac.util.*; import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; import com.sun.tools.javac.util.List; @@ -45,7 +43,6 @@ import com.sun.tools.javac.tree.JCTree.*; import static com.sun.tools.javac.code.Flags.*; import static com.sun.tools.javac.code.Kinds.Kind.*; -import static com.sun.tools.javac.code.Scope.LookupKind.NON_RECURSIVE; import static com.sun.tools.javac.code.TypeTag.*; import static com.sun.tools.javac.jvm.ByteCodes.*; import static com.sun.tools.javac.jvm.CRTFlags.*; @@ -69,12 +66,12 @@ public class Gen extends JCTree.Visitor { private final TreeMaker make; private final Names names; private final Target target; - private final Map stringBufferAppend; private Name accessDollar; private final Types types; private final Lower lower; private final Flow flow; private final Annotate annotate; + private final StringConcat concat; /** Format of stackmap tables to be generated. */ private final Code.StackMapFormat stackMap; @@ -105,8 +102,9 @@ public class Gen extends JCTree.Visitor { make = TreeMaker.instance(context); target = Target.instance(context); types = Types.instance(context); + concat = StringConcat.instance(context); + methodType = new MethodType(null, null, null, syms.methodClass); - stringBufferAppend = new HashMap<>(); accessDollar = names. fromString("access" + target.syntheticNameChar()); flow = Flow.instance(context); @@ -753,6 +751,18 @@ public class Gen extends JCTree.Visitor { } } + public Code getCode() { + return code; + } + + public Items getItems() { + return items; + } + + public Env getAttrEnv() { + return attrEnv; + } + /** Visitor class for expressions which might be constant expressions. * This class is a subset of TreeScanner. Intended to visit trees pruned by * Lower as long as constant expressions looking for references to any @@ -1895,25 +1905,7 @@ public class Gen extends JCTree.Visitor { OperatorSymbol operator = (OperatorSymbol) tree.operator; Item l; if (operator.opcode == string_add) { - // Generate code to make a string buffer - makeStringBuffer(tree.pos()); - - // Generate code for first string, possibly save one - // copy under buffer - l = genExpr(tree.lhs, tree.lhs.type); - if (l.width() > 0) { - code.emitop0(dup_x1 + 3 * (l.width() - 1)); - } - - // Load first string and append to buffer. - l.load(); - appendString(tree.lhs); - - // Append all other strings to buffer. - appendStrings(tree.rhs); - - // Convert buffer to string. - bufferToString(tree.pos()); + l = concat.makeConcat(tree); } else { // Generate code for first expression l = genExpr(tree.lhs, tree.lhs.type); @@ -2026,13 +2018,7 @@ public class Gen extends JCTree.Visitor { public void visitBinary(JCBinary tree) { OperatorSymbol operator = (OperatorSymbol)tree.operator; if (operator.opcode == string_add) { - // Create a string buffer. - makeStringBuffer(tree.pos()); - // Append all strings to buffer. - appendStrings(tree); - // Convert buffer to string. - bufferToString(tree.pos()); - result = items.makeStackItem(syms.stringType); + result = concat.makeConcat(tree); } else if (tree.hasTag(AND)) { CondItem lcond = genCond(tree.lhs, CRT_FLOW_CONTROLLER); if (!lcond.isFalse()) { @@ -2066,67 +2052,7 @@ public class Gen extends JCTree.Visitor { result = completeBinop(tree.lhs, tree.rhs, operator); } } -//where - /** Make a new string buffer. - */ - void makeStringBuffer(DiagnosticPosition pos) { - code.emitop2(new_, makeRef(pos, syms.stringBuilderType)); - code.emitop0(dup); - callMethod( - pos, syms.stringBuilderType, names.init, List.nil(), false); - } - /** Append value (on tos) to string buffer (on tos - 1). - */ - void appendString(JCTree tree) { - Type t = tree.type.baseType(); - if (!t.isPrimitive() && t.tsym != syms.stringType.tsym) { - t = syms.objectType; - } - items.makeMemberItem(getStringBufferAppend(tree, t), false).invoke(); - } - Symbol getStringBufferAppend(JCTree tree, Type t) { - Assert.checkNull(t.constValue()); - Symbol method = stringBufferAppend.get(t); - if (method == null) { - method = rs.resolveInternalMethod(tree.pos(), - attrEnv, - syms.stringBuilderType, - names.append, - List.of(t), - null); - stringBufferAppend.put(t, method); - } - return method; - } - - /** Add all strings in tree to string buffer. - */ - void appendStrings(JCTree tree) { - tree = TreeInfo.skipParens(tree); - if (tree.hasTag(PLUS) && tree.type.constValue() == null) { - JCBinary op = (JCBinary) tree; - if (op.operator.kind == MTH && - ((OperatorSymbol) op.operator).opcode == string_add) { - appendStrings(op.lhs); - appendStrings(op.rhs); - return; - } - } - genExpr(tree, tree.type).load(); - appendString(tree); - } - - /** Convert string buffer on tos to string. - */ - void bufferToString(DiagnosticPosition pos) { - callMethod( - pos, - syms.stringBuilderType, - names.toString, - List.nil(), - false); - } /** Complete generating code for operation, with left operand * already on stack. @@ -2173,8 +2099,8 @@ public class Gen extends JCTree.Visitor { } public void visitTypeCast(JCTypeCast tree) { - setTypeAnnotationPositions(tree.pos); result = genExpr(tree.expr, tree.clazz.type).load(); + setTypeAnnotationPositions(tree.pos); // Additional code is only needed if we cast to a reference type // which is not statically a supertype of the expression's type. // For basic types, the coerce(...) in genExpr(...) will do @@ -2191,8 +2117,8 @@ public class Gen extends JCTree.Visitor { } public void visitTypeTest(JCInstanceOf tree) { - setTypeAnnotationPositions(tree.pos); genExpr(tree.expr, tree.expr.type).load(); + setTypeAnnotationPositions(tree.pos); code.emitop2(instanceof_, makeRef(tree.pos(), tree.clazz.type)); result = items.makeStackItem(syms.booleanType); } diff --git a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/StringConcat.java b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/StringConcat.java new file mode 100644 index 00000000000..bb5436bd96c --- /dev/null +++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/StringConcat.java @@ -0,0 +1,485 @@ +/* + * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.jvm; + +import com.sun.tools.javac.code.*; +import com.sun.tools.javac.comp.Resolve; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.TreeInfo; +import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.util.*; + +import static com.sun.tools.javac.code.Kinds.Kind.MTH; +import static com.sun.tools.javac.code.TypeTag.DOUBLE; +import static com.sun.tools.javac.code.TypeTag.LONG; +import static com.sun.tools.javac.jvm.ByteCodes.*; +import static com.sun.tools.javac.tree.JCTree.Tag.PLUS; +import com.sun.tools.javac.jvm.Items.*; + +import java.util.HashMap; +import java.util.Map; + +/** This lowers the String concatenation to something that JVM can understand. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public abstract class StringConcat { + + /** + * Maximum number of slots for String Concat call. + * JDK's StringConcatFactory does not support more than that. + */ + private static final int MAX_INDY_CONCAT_ARG_SLOTS = 200; + private static final char TAG_ARG = '\u0001'; + private static final char TAG_CONST = '\u0002'; + + protected final Gen gen; + protected final Symtab syms; + protected final Names names; + protected final TreeMaker make; + protected final Types types; + protected final Map sbAppends; + protected final Resolve rs; + + protected static final Context.Key concatKey = new Context.Key<>(); + + public static StringConcat instance(Context context) { + StringConcat instance = context.get(concatKey); + if (instance == null) { + instance = makeConcat(context); + } + return instance; + } + + private static StringConcat makeConcat(Context context) { + Target target = Target.instance(context); + String opt = Options.instance(context).get("stringConcat"); + if (target.hasStringConcatFactory()) { + if (opt == null) { + opt = "indyWithConstants"; + } + } else { + if (opt != null && !"inline".equals(opt)) { + Assert.error("StringConcatFactory-based string concat is requested on a platform that does not support it."); + } + opt = "inline"; + } + + switch (opt) { + case "inline": + return new Inline(context); + case "indy": + return new IndyPlain(context); + case "indyWithConstants": + return new IndyConstants(context); + default: + Assert.error("Unknown stringConcat: " + opt); + throw new IllegalStateException("Unknown stringConcat: " + opt); + } + } + + protected StringConcat(Context context) { + context.put(concatKey, this); + gen = Gen.instance(context); + syms = Symtab.instance(context); + types = Types.instance(context); + names = Names.instance(context); + make = TreeMaker.instance(context); + rs = Resolve.instance(context); + sbAppends = new HashMap<>(); + } + + public abstract Item makeConcat(JCTree.JCAssignOp tree); + public abstract Item makeConcat(JCTree.JCBinary tree); + + protected List collectAll(JCTree tree) { + return collect(tree, List.nil()); + } + + protected List collectAll(JCTree.JCExpression lhs, JCTree.JCExpression rhs) { + return List.nil() + .appendList(collectAll(lhs)) + .appendList(collectAll(rhs)); + } + + private List collect(JCTree tree, List res) { + tree = TreeInfo.skipParens(tree); + if (tree.hasTag(PLUS) && tree.type.constValue() == null) { + JCTree.JCBinary op = (JCTree.JCBinary) tree; + if (op.operator.kind == MTH && + ((Symbol.OperatorSymbol) op.operator).opcode == string_add) { + return res + .appendList(collect(op.lhs, res)) + .appendList(collect(op.rhs, res)); + } + } + return res.append(tree); + } + + /** + * "Legacy" bytecode flavor: emit the StringBuilder.append chains for string + * concatenation. + */ + private static class Inline extends StringConcat { + public Inline(Context context) { + super(context); + } + + @Override + public Item makeConcat(JCTree.JCAssignOp tree) { + // Generate code to make a string builder + JCDiagnostic.DiagnosticPosition pos = tree.pos(); + + // Create a string builder. + newStringBuilder(tree); + + // Generate code for first string, possibly save one + // copy under builder + Item l = gen.genExpr(tree.lhs, tree.lhs.type); + if (l.width() > 0) { + gen.getCode().emitop0(dup_x1 + 3 * (l.width() - 1)); + } + + // Load first string and append to builder. + l.load(); + appendString(tree.lhs); + + // Append all other strings to builder. + List args = collectAll(tree.rhs); + for (JCTree t : args) { + gen.genExpr(t, t.type).load(); + appendString(t); + } + + // Convert builder to string. + builderToString(pos); + + return l; + } + + @Override + public Item makeConcat(JCTree.JCBinary tree) { + JCDiagnostic.DiagnosticPosition pos = tree.pos(); + + // Create a string builder. + newStringBuilder(tree); + + // Append all strings to builder. + List args = collectAll(tree); + for (JCTree t : args) { + gen.genExpr(t, t.type).load(); + appendString(t); + } + + // Convert builder to string. + builderToString(pos); + + return gen.getItems().makeStackItem(syms.stringType); + } + + private JCDiagnostic.DiagnosticPosition newStringBuilder(JCTree tree) { + JCDiagnostic.DiagnosticPosition pos = tree.pos(); + gen.getCode().emitop2(new_, gen.makeRef(pos, syms.stringBuilderType)); + gen.getCode().emitop0(dup); + gen.callMethod(pos, syms.stringBuilderType, names.init, List.nil(), false); + return pos; + } + + private void appendString(JCTree tree) { + Type t = tree.type.baseType(); + if (!t.isPrimitive() && t.tsym != syms.stringType.tsym) { + t = syms.objectType; + } + + Assert.checkNull(t.constValue()); + Symbol method = sbAppends.get(t); + if (method == null) { + method = rs.resolveInternalMethod(tree.pos(), gen.getAttrEnv(), syms.stringBuilderType, names.append, List.of(t), null); + sbAppends.put(t, method); + } + + gen.getItems().makeMemberItem(method, false).invoke(); + } + + private void builderToString(JCDiagnostic.DiagnosticPosition pos) { + gen.callMethod(pos, syms.stringBuilderType, names.toString, List.nil(), false); + } + } + + /** + * Base class for indified concatenation bytecode flavors. + */ + private static abstract class Indy extends StringConcat { + public Indy(Context context) { + super(context); + } + + @Override + public Item makeConcat(JCTree.JCAssignOp tree) { + List args = collectAll(tree.lhs, tree.rhs); + Item l = gen.genExpr(tree.lhs, tree.lhs.type); + emit(args, tree.type, tree.pos()); + return l; + } + + @Override + public Item makeConcat(JCTree.JCBinary tree) { + List args = collectAll(tree.lhs, tree.rhs); + emit(args, tree.type, tree.pos()); + return gen.getItems().makeStackItem(syms.stringType); + } + + protected abstract void emit(List args, Type type, JCDiagnostic.DiagnosticPosition pos); + + /** Peel the argument list into smaller chunks. */ + protected List> split(List args) { + ListBuffer> splits = new ListBuffer<>(); + + int slots = 0; + + // Need to peel, so that neither call has more than acceptable number + // of slots for the arguments. + ListBuffer cArgs = new ListBuffer<>(); + for (JCTree t : args) { + int needSlots = (t.type.getTag() == LONG || t.type.getTag() == DOUBLE) ? 2 : 1; + if (slots + needSlots >= MAX_INDY_CONCAT_ARG_SLOTS) { + splits.add(cArgs.toList()); + cArgs.clear(); + slots = 0; + } + cArgs.add(t); + slots += needSlots; + } + + // Flush the tail slice + if (!cArgs.isEmpty()) { + splits.add(cArgs.toList()); + } + + return splits.toList(); + } + } + + /** + * Emits the invokedynamic call to JDK java.lang.invoke.StringConcatFactory, + * without handling constants specially. + * + * We bypass empty strings, because they have no meaning at this level. This + * captures the Java language trick to force String concat with e.g. ("" + int)-like + * expression. Down here, we already know we are in String concat business, and do + * not require these markers. + */ + private static class IndyPlain extends Indy { + public IndyPlain(Context context) { + super(context); + } + + /** Emit the indy concat for all these arguments, possibly peeling along the way */ + protected void emit(List args, Type type, JCDiagnostic.DiagnosticPosition pos) { + List> split = split(args); + + for (List t : split) { + Assert.check(!t.isEmpty(), "Arguments list is empty"); + + ListBuffer dynamicArgs = new ListBuffer<>(); + for (JCTree arg : t) { + Object constVal = arg.type.constValue(); + if ("".equals(constVal)) continue; + if (arg.type == syms.botType) { + dynamicArgs.add(types.boxedClass(syms.voidType).type); + } else { + dynamicArgs.add(arg.type); + } + gen.genExpr(arg, arg.type).load(); + } + + doCall(type, pos, dynamicArgs.toList()); + } + + // More that one peel slice produced: concatenate the results + if (split.size() > 1) { + ListBuffer argTypes = new ListBuffer<>(); + for (int c = 0; c < split.size(); c++) { + argTypes.append(syms.stringType); + } + doCall(type, pos, argTypes.toList()); + } + } + + /** Produce the actual invokedynamic call to StringConcatFactory */ + private void doCall(Type type, JCDiagnostic.DiagnosticPosition pos, List dynamicArgTypes) { + Type.MethodType indyType = new Type.MethodType(dynamicArgTypes, + type, + List.nil(), + syms.methodClass); + + int prevPos = make.pos; + try { + make.at(pos); + + List bsm_staticArgs = List.of(syms.methodHandleLookupType, + syms.stringType, + syms.methodTypeType); + + Symbol bsm = rs.resolveInternalMethod(pos, + gen.getAttrEnv(), + syms.stringConcatFactory, + names.makeConcat, + bsm_staticArgs, + null); + + Symbol.DynamicMethodSymbol dynSym = new Symbol.DynamicMethodSymbol(names.makeConcat, + syms.noSymbol, + ClassFile.REF_invokeStatic, + (Symbol.MethodSymbol)bsm, + indyType, + List.nil().toArray()); + + Items.Item item = gen.getItems().makeDynamicItem(dynSym); + item.invoke(); + } finally { + make.at(prevPos); + } + } + } + + /** + * Emits the invokedynamic call to JDK java.lang.invoke.StringConcatFactory. + * This code concatenates all known constants into the recipe, possibly escaping + * some constants separately. + * + * We also bypass empty strings, because they have no meaning at this level. This + * captures the Java language trick to force String concat with e.g. ("" + int)-like + * expression. Down here, we already know we are in String concat business, and do + * not require these markers. + */ + private static final class IndyConstants extends Indy { + public IndyConstants(Context context) { + super(context); + } + + @Override + protected void emit(List args, Type type, JCDiagnostic.DiagnosticPosition pos) { + List> split = split(args); + + for (List t : split) { + Assert.check(!t.isEmpty(), "Arguments list is empty"); + + StringBuilder recipe = new StringBuilder(t.size()); + ListBuffer dynamicArgs = new ListBuffer<>(); + ListBuffer staticArgs = new ListBuffer<>(); + + for (JCTree arg : t) { + Object constVal = arg.type.constValue(); + if ("".equals(constVal)) continue; + if (arg.type == syms.botType) { + // Concat the null into the recipe right away + recipe.append((String) null); + } else if (constVal != null) { + // Concat the String representation of the constant, except + // for the case it contains special tags, which requires us + // to expose it as detached constant. + String a = arg.type.stringValue(); + if (a.indexOf(TAG_CONST) != -1 || a.indexOf(TAG_ARG) != -1) { + recipe.append(TAG_CONST); + staticArgs.add(a); + } else { + recipe.append(a); + } + } else { + // Ordinary arguments come through the dynamic arguments. + recipe.append(TAG_ARG); + dynamicArgs.add(arg.type); + gen.genExpr(arg, arg.type).load(); + } + } + + doCall(type, pos, recipe.toString(), staticArgs.toList(), dynamicArgs.toList()); + } + + // More that one peel slice produced: concatenate the results + // All arguments are assumed to be non-constant Strings. + if (split.size() > 1) { + ListBuffer argTypes = new ListBuffer<>(); + StringBuilder recipe = new StringBuilder(); + for (int c = 0; c < split.size(); c++) { + argTypes.append(syms.stringType); + recipe.append(TAG_ARG); + } + doCall(type, pos, recipe.toString(), List.nil(), argTypes.toList()); + } + } + + /** Produce the actual invokedynamic call to StringConcatFactory */ + private void doCall(Type type, JCDiagnostic.DiagnosticPosition pos, String recipe, List staticArgs, List dynamicArgTypes) { + Type.MethodType indyType = new Type.MethodType(dynamicArgTypes, + type, + List.nil(), + syms.methodClass); + + int prevPos = make.pos; + try { + make.at(pos); + + ListBuffer constTypes = new ListBuffer<>(); + ListBuffer constants = new ListBuffer<>(); + for (Object t : staticArgs) { + constants.add(t); + constTypes.add(syms.stringType); + } + + List bsm_staticArgs = List.of(syms.methodHandleLookupType, + syms.stringType, + syms.methodTypeType) + .append(syms.stringType) + .appendList(constTypes); + + Symbol bsm = rs.resolveInternalMethod(pos, + gen.getAttrEnv(), + syms.stringConcatFactory, + names.makeConcatWithConstants, + bsm_staticArgs, + null); + + Symbol.DynamicMethodSymbol dynSym = new Symbol.DynamicMethodSymbol(names.makeConcatWithConstants, + syms.noSymbol, + ClassFile.REF_invokeStatic, + (Symbol.MethodSymbol)bsm, + indyType, + List.of(recipe).appendList(constants).toArray()); + + Items.Item item = gen.getItems().makeDynamicItem(dynSym); + item.invoke(); + } finally { + make.at(prevPos); + } + } + } + +} diff --git a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java index aeb2b11adc2..df70c50b8ec 100644 --- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java +++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java @@ -135,4 +135,10 @@ public enum Target { return hasInvokedynamic(); } + /** Does the target JDK contain StringConcatFactory class? + */ + public boolean hasStringConcatFactory() { + return compareTo(JDK1_9) >= 0; + } + } diff --git a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java index 4f5b11f07a5..213277432c0 100644 --- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java +++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java @@ -179,6 +179,10 @@ public class Names { public final Name altMetafactory; public final Name dollarThis; + // string concat + public final Name makeConcat; + public final Name makeConcatWithConstants; + public final Name.Table table; public Names(Context context) { @@ -316,6 +320,10 @@ public class Names { lambda = fromString("lambda$"); metafactory = fromString("metafactory"); altMetafactory = fromString("altMetafactory"); + + // string concat + makeConcat = fromString("makeConcat"); + makeConcatWithConstants = fromString("makeConcatWithConstants"); } protected Name.Table createTable(Options options) { diff --git a/langtools/src/jdk.javadoc/share/classes/com/sun/javadoc/package-info.java b/langtools/src/jdk.javadoc/share/classes/com/sun/javadoc/package-info.java index ace69c00622..36a9f5c224c 100644 --- a/langtools/src/jdk.javadoc/share/classes/com/sun/javadoc/package-info.java +++ b/langtools/src/jdk.javadoc/share/classes/com/sun/javadoc/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,6 +24,11 @@ */ /** +

+Note: The declarations in this package have been superseded by those +in the package {@code jdk.javadoc.doclet}. +

+ The Doclet API (also called the Javadoc API) provides a mechanism for clients to inspect the source-level structure of programs and libraries, including javadoc comments embedded in the source. diff --git a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/Taglet.java b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/Taglet.java index 5a41b1d654e..16578ea9ada 100644 --- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/Taglet.java +++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/Taglet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2006, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,7 +29,14 @@ import com.sun.javadoc.*; /** * The interface for a custom tag used by Doclets. A custom - * tag must implement this interface. To be loaded and used by + * tag must implement this interface. + * + *

+ * Note: This interface has been superseded by one + * in the new package {@code jdk.javadoc.doclet.taglet}. + *

+ * + * To be loaded and used by * doclets at run-time, the taglet must have a static method called * register that accepts a {@link java.util.Map} as an * argument with the following signature: diff --git a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/markup/package-info.java b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/markup/package-info.java index 7c8766c4f69..a9eb28cbea5 100644 --- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/markup/package-info.java +++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/markup/package-info.java @@ -31,4 +31,5 @@ This code and its internal interfaces are subject to change or deletion without notice. */ + package com.sun.tools.doclets.formats.html.markup; diff --git a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/package-info.java b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/package-info.java index dadc9e1500d..d2f2618e64d 100644 --- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/package-info.java +++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,8 +24,7 @@ */ /** - This is the default doclet provided with JDK that produces Javadoc's - default HTML-formatted API output. For more documentation + This produces Javadoc's HTML-formatted API output. For more documentation on this doclet, please refer to the link below. @see @@ -36,4 +35,5 @@ This code and its internal interfaces are subject to change or deletion without notice. */ + package com.sun.tools.doclets.formats.html; diff --git a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/Configuration.java b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/Configuration.java index 27154a72162..07936a8d1c9 100644 --- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/Configuration.java +++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/Configuration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,6 +29,7 @@ import java.io.*; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; + import javax.tools.JavaFileManager; import com.sun.javadoc.*; @@ -37,6 +38,7 @@ import com.sun.tools.javac.jvm.Profile; import com.sun.tools.doclets.internal.toolkit.builders.BuilderFactory; import com.sun.tools.doclets.internal.toolkit.taglets.*; import com.sun.tools.doclets.internal.toolkit.util.*; +import com.sun.tools.doclets.internal.toolkit.util.VisibleMemberMap.GetterSetter; import com.sun.tools.javac.util.StringUtils; /** @@ -304,6 +306,13 @@ public abstract class Configuration { */ public SortedSet packages; + // The following three fields provide caches for use by all instances of VisibleMemberMap. + public final Map propertiesCache = new HashMap<>(); + public final Map classPropertiesMap = new HashMap<>(); + public final Map getterSetterMap = new HashMap<>(); + + public DocFileFactory docFileFactory; + /** * Constructor. Constructs the message retriever with resource file. */ diff --git a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/builders/package-info.java b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/builders/package-info.java index ef8b4acf1c1..c11193fc36a 100644 --- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/builders/package-info.java +++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/builders/package-info.java @@ -37,4 +37,5 @@ This code and its internal interfaces are subject to change or deletion without notice. */ + package com.sun.tools.doclets.internal.toolkit.builders; diff --git a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/package-info.java b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/package-info.java index e8bcae2fed3..30bc952e01b 100644 --- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/package-info.java +++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/package-info.java @@ -53,4 +53,5 @@ This code and its internal interfaces are subject to change or deletion without notice. */ + package com.sun.tools.doclets.internal.toolkit; diff --git a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/taglets/package-info.java b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/taglets/package-info.java index 43750f1d774..cad2426bf1f 100644 --- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/taglets/package-info.java +++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/taglets/package-info.java @@ -48,4 +48,5 @@ This code and its internal interfaces are subject to change or deletion without notice. */ + package com.sun.tools.doclets.internal.toolkit.taglets; diff --git a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/util/DocFileFactory.java b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/util/DocFileFactory.java index db34c5cc5c9..7c0b88db18c 100644 --- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/util/DocFileFactory.java +++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/util/DocFileFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,9 +25,6 @@ package com.sun.tools.doclets.internal.toolkit.util; -import java.util.Map; -import java.util.WeakHashMap; - import javax.tools.JavaFileManager; import javax.tools.JavaFileManager.Location; import javax.tools.StandardJavaFileManager; @@ -45,15 +42,13 @@ import com.sun.tools.doclets.internal.toolkit.Configuration; * * @since 1.8 */ -abstract class DocFileFactory { - private static final Map factories = new WeakHashMap<>(); - +public abstract class DocFileFactory { /** * Get the appropriate factory, based on the file manager given in the * configuration. */ static synchronized DocFileFactory getFactory(Configuration configuration) { - DocFileFactory f = factories.get(configuration); + DocFileFactory f = configuration.docFileFactory; if (f == null) { JavaFileManager fm = configuration.getFileManager(); if (fm instanceof StandardJavaFileManager) { @@ -61,7 +56,7 @@ abstract class DocFileFactory { } else { throw new IllegalStateException(); } - factories.put(configuration, f); + configuration.docFileFactory = f; } return f; } diff --git a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/util/VisibleMemberMap.java b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/util/VisibleMemberMap.java index 487215ca4a0..7a1abfb1eec 100644 --- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/util/VisibleMemberMap.java +++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/util/VisibleMemberMap.java @@ -101,9 +101,9 @@ public class VisibleMemberMap { private final Configuration configuration; private final Utils utils; - private static final Map propertiesCache = new HashMap<>(); - private static final Map classPropertiesMap = new HashMap<>(); - private static final Map getterSetterMap = new HashMap<>(); + private final Map propertiesCache; + private final Map classPropertiesMap; + private final Map getterSetterMap; /** * Construct a VisibleMemberMap of the given type for the given @@ -123,6 +123,9 @@ public class VisibleMemberMap { this.kind = kind; this.configuration = configuration; this.utils = configuration.utils; + propertiesCache = configuration.propertiesCache; + classPropertiesMap = configuration.classPropertiesMap; + getterSetterMap = configuration.getterSetterMap; new ClassMembers(classdoc, STARTLEVEL).build(); } @@ -713,7 +716,7 @@ public class VisibleMemberMap { } } - private class GetterSetter { + public class GetterSetter { private final ProgramElementDoc getter; private final ProgramElementDoc setter; diff --git a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/util/links/package-info.java b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/util/links/package-info.java index 1319ccd0877..da4d9455716 100644 --- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/util/links/package-info.java +++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/util/links/package-info.java @@ -31,4 +31,5 @@ This code and its internal interfaces are subject to change or deletion without notice. */ + package com.sun.tools.doclets.internal.toolkit.util.links; diff --git a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/util/package-info.java b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/util/package-info.java index 2e898013ffb..ef49b0ab1e2 100644 --- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/util/package-info.java +++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/util/package-info.java @@ -32,4 +32,5 @@ This code and its internal interfaces are subject to change or deletion without notice. */ + package com.sun.tools.doclets.internal.toolkit.util; diff --git a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/package-info.java b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/package-info.java index 0ced35a2c62..735bfa5c5cc 100644 --- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/package-info.java +++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,6 +24,11 @@ */ /** +

+Note: The declarations in this package have been superseded by those +in the new package {@code jdk.javadoc.doclet}. +

+ As of JDK version 1.5, replaced by {@code com.sun.tools.doclets.internal.toolkit.util}. @@ -32,4 +37,5 @@ This code and its internal interfaces are subject to change or deletion without notice. */ + package com.sun.tools.doclets; diff --git a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/Start.java b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/Start.java index a1c31b6e722..9e3419f5449 100644 --- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/Start.java +++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/Start.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -103,7 +103,11 @@ public class Start extends ToolOption.Helper { this(programName, errWriter, warnWriter, noticeWriter, defaultDocletClassName, null); } - Start(String programName, + public Start(PrintWriter pw) { + this(javadocName, pw, pw, pw, standardDocletClassName); + } + + public Start(String programName, PrintWriter errWriter, PrintWriter warnWriter, PrintWriter noticeWriter, @@ -139,7 +143,7 @@ public class Start extends ToolOption.Helper { this(javadocName, docletParentClassLoader); } - Start() { + public Start() { this(javadocName); } @@ -212,7 +216,7 @@ public class Start extends ToolOption.Helper { /** * Main program - external wrapper */ - int begin(String... argv) { + public int begin(String... argv) { boolean ok = begin(null, argv, Collections. emptySet()); return ok ? 0 : 1; } diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/doclet/Doclet.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/doclet/Doclet.java new file mode 100644 index 00000000000..9e7885675a6 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/doclet/Doclet.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.doclet; + +import java.util.ListIterator; +import java.util.Locale; +import java.util.Set; + +import javax.lang.model.SourceVersion; + +/** + * The user doclet must implement this interface, as described in the + *
package description. + * Each implementation of a Doclet must provide a public no-argument constructor + * to be used by tools to instantiate the doclet. The tool infrastructure will + * interact with classes implementing this interface as follows: + *
    + *
  1. The tool will create an instance of a doclet using the no-arg constructor + * of the doclet class. + *
  2. Next, the tool calls the {@link #init init} method with an appropriate locale + * and reporter. + *
  3. Afterwards, the tool calls {@link #getSupportedOptions getSupportedOptions}, + * and {@link #getSupportedSourceVersion getSupportedSourceVersion}. + * These methods are only called once. + *
  4. As appropriate, the tool calls the {@link #run run} method on the doclet + * object, giving it a DocletEnvironment object, from which the doclet can determine + * the elements to be included in the documentation. + *
+ *

+ * If a doclet object is created and used without the above protocol being followed, + * then the doclet's behavior is not defined by this interface specification. + *

To start the doclet, pass {@code -doclet} followed by the fully-qualified + * name of the entry point class (i.e. the implementation of this interface) on + * the javadoc tool command line. + * + * @since 9 + */ +public interface Doclet { + + /** + * Initializes this doclet with the given locale and error reporter. + * This locale will be used by the reporter and the doclet components. + * It is recommended to call this as early as possible, for a + * uniform localized user experience, + * @param locale the locale to be used + * @param reporter the reporter to be used + */ + public void init(Locale locale, Reporter reporter); + + /** + * Returns a name identifying the doclet. A name is a simple identifier + * without white spaces, as defined in The Java™ Language Specification, + * section 6.2 "Names and Identifiers". + * @return name of the Doclet + */ + public abstract String getName(); + + /** + * Returns all the supported options. + * + * @return a Set containing all the supported options, an empty set if none. + */ + public Set

+ * Note: The declarations in this package supersede those + * in the older package {@code com.sun.javadoc}. For details on the + * mapping of old types to new types, see the + * Migration Guide. + *

+ * + *

+ * Doclets are invoked by javadoc and this API can be used to write out + * program information to files. For example, the standard doclet is + * invoked by default, to generate HTML documentation. + *

+ + * The invocation is defined by the interface {@link jdk.javadoc.doclet.Doclet} + * -- the {@link jdk.javadoc.doclet.Doclet#run(DocletEnvironment) run} interface + * method, defines the entry point. + *

+ *    public boolean run(DocletEnvironment environment)
+ * 
+ * The {@link jdk.javadoc.doclet.DocletEnvironment} instance holds the environment that the + * doclet will be initialized with. From this environment all other information can be + * extracted, in the form of {@link javax.lang.model} elements. One can further use the + * APIs and utilities described by {@link javax.lang.model} to query Elements and Types. + *

+ * + * + *

Terminology

+ * + * + * When calling javadoc, one can pass in package names and source file names -- + * these are called the specified PackageElements and TypeElements. + * Javadoc options are also passed in; the access control Javadoc options + * ({@code -public}, {@code -protected}, {@code -package}, + * and {@code -private}) are used to filter program elements, producing a + * result set, called the included set, or "selected" set. + *

+ + * + * A qualified element name is one that has its package + * name prepended to it, such as {@code java.lang.String}. A non-qualified + * name has no package name, such as {@code String}. + *

+ * + * + *

Example

+ * + * The following is an example doclet that displays information of a class + * and its members, supporting an option "someoption". + *
+ * import com.sun.source.doctree.DocCommentTree;
+ * import com.sun.source.util.DocTrees;
+ * import java.io.IOException;
+ * import java.util.Collections;
+ * import java.util.Set;
+ * import javax.lang.model.SourceVersion;
+ * import javax.lang.model.element.Element;
+ * import javax.lang.model.element.TypeElement;
+ * import jdk.javadoc.doclet.*;
+ *
+ * public class Example implements Doclet {
+ *
+ *     @Override
+ *     public void init(Locale locale, Reporter reporter) {
+ *        return;
+ *     }
+ *
+ *     @Override
+ *     public boolean run(RootDoc root) {
+ *         // cache the DocTrees utility class to access DocComments
+ *         DocTrees docTrees = root.getDocTrees();
+ *
+ *         // location of an element in the same directory as overview.html
+ *         try {
+ *             Element barElement = null;
+ *             for (Element e : root.getIncludedClasses()) {
+ *                 if (e.getSimpleName().toString().equals("FooBar")) {
+ *                     barElement = e;
+ *                 }
+ *             }
+ *             DocCommentTree docCommentTree =
+ *                     docTrees.getDocCommentTree(barElement, "overview.html");
+ *             if (docCommentTree != null) {
+ *                 System.out.println("Overview html: " +
+ *                         docCommentTree.getFullBody());
+ *             }
+ *         } catch (IOException missing) {
+ *             System.err.println("No overview.html found.");
+ *         }
+ *
+ *         for (TypeElement t : root.getIncludedClasses()) {
+ *             System.out.println(t.getKind() + ":" + t);
+ *             for (Element e : t.getEnclosedElements()) {
+ *                 DocCommentTree docCommentTree = docTrees.getDocCommentTree(e);
+ *                 if (docCommentTree != null) {
+ *                     System.out.println("Element (" + e.getKind() + ": " +
+ *                             e + ") has the following comments:");
+ *                     System.out.println("Entire body: " + docCommentTree.getFullBody());
+ *                     System.out.println("Block tags: " + docCommentTree.getBlockTags());
+ *                 } else {
+ *                     System.out.println("no comment.");
+ *                 }
+ *             }
+ *         }
+ *         return true;
+ *     }
+ *
+ *     @Override
+ *     public String getName() {
+ *         return "Example";
+ *     }
+ *
+ *   private String someOption;
+ *
+ *   @Override
+ *   public Set<Option> getSupportedOptions() {
+ *       Option[] options = {
+ *           new Option() {
+ *               public int getArgumentCount() {
+ *                   return 1;
+ *               }
+ *               public String getDescription() {
+ *                   return "someoption";
+ *               }
+ *               public Option.Kind getKind() {
+ *                   return Option.Kind.STANDARD;
+ *               }
+ *               public String getName() {
+ *                   return "someoption";
+ *               }
+ *               public String getParameters() {
+ *                   return "url";
+ *               }
+ *               public boolean matches(String option) {
+ *                  String opt = option.startsWith("-") ? option.substring(1) : option;
+ *                  return getName().equals(opt);
+ *               }
+ *               public boolean process(String option, ListIterator<String> arguments) {
+ *                  overviewpath = arguments.next();
+ *                  return true;
+ *               }
+ *          }
+ *      };
+ *      return new HashSet<Option>(Arrays.asList(options));
+ *     }
+ *
+ *     @Override
+ *     public SourceVersion getSupportedSourceVersion() {
+ *         // support the latest release
+ *         return SourceVersion.latest();
+ *     }
+ * }
+ * 
+ *

+ * This doclet when invoked with a command line, such as: + *

+ *     javadoc -doclet Example -sourcepath <source-location>
+ * 
+ * will produce an output, such as: + *
+ *  Overview.html: overview comments
+ *  ...
+ *  ...
+ *  CLASS: SomeKlass
+ *  .....
+ *  Element (METHOD: main(java.lang.String...)) has the following comments:
+ *  Entire body: The main entry point.
+ *  Block tags: @param an array of Strings
+ *  ...
+ * 
+ * + *

Migration Guide

+ * + *

Many of the types in the old {@code com.sun.javadoc} API do not have equivalents in this + * package. Instead, types in the {@code javax.lang.model} and {@code com.sun.source} APIs + * are used instead. + * + *

The following table gives a guide to the mapping from old types to their replacements. + * In some cases, there is no direct equivalent. + * + * + +
Guide for mapping old types to new types
Old TypeNew Type +
AnnotatedTypejavax.lang.model.type.Type +
AnnotationDescjavax.lang.model.element.AnnotationMirror +
AnnotationDesc.ElementValuePairjavax.lang.model.element.AnnotationValue +
AnnotationTypeDocjavax.lang.model.element.TypeElement +
AnnotationTypeElementDocjavax.lang.model.element.ExecutableElement +
AnnotationValuejavax.lang.model.element.AnnotationValue +
ClassDocjavax.lang.model.element.TypeElement +
ConstructorDocjavax.lang.model.element.ExecutableElement +
Docjavax.lang.model.element.Element +
DocErrorReporterjdk.javadoc.doclet.Reporter +
Docletjdk.javadoc.doclet.Doclet +
ExecutableMemberDocjavax.lang.model.element.ExecutableElement +
FieldDocjavax.lang.model.element.VariableElement +
LanguageVersionjavax.lang.model.SourceVersion +
MemberDocjavax.lang.model.element.Element +
MethodDocjavax.lang.model.element.ExecutableElement +
PackageDocjavax.lang.model.element.PackageElement +
Parameterjavax.lang.model.element.VariableElement +
ParameterizedTypejavax.lang.model.type.DeclaredType +
ParamTagcom.sun.source.doctree.ParamTree +
ProgramElementDocjavax.lang.model.element.Element +
RootDocjdk.javadoc.doclet.DocletEnvironment +
SeeTagcom.sun.source.doctree.LinkTree
com.sun.source.doctree.SeeTree +
SerialFieldTagcom.sun.source.doctree.SerialFieldTree +
SourcePositioncom.sun.source.util.SourcePositions +
Tagcom.sun.source.doctree.DocTree +
ThrowsTagcom.sun.source.doctree.ThrowsTree +
Typejavax.lang.model.type.Type +
TypeVariablejavax.lang.model.type.TypeVariable +
WildcardTypejavax.lang.model.type.WildcardType + *
+ * + * @see jdk.javadoc.doclet.Doclet + * @see jdk.javadoc.doclet.DocletEnvironment + * @since 9 +*/ + +package jdk.javadoc.doclet; diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/doclet/taglet/Taglet.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/doclet/taglet/Taglet.java new file mode 100644 index 00000000000..f102bd55314 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/doclet/taglet/Taglet.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2001, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.doclet.taglet; + +import java.util.List; +import java.util.Set; + +import com.sun.source.doctree.DocTree; + +/** + * The interface for a custom tag used by Doclets. A custom + * tag must implement this interface, and must have a public + * default constructor (i.e. a public constructor with no + * parameters), by which, the doclet will instantiate and + * register the custom tag. + * + * @since 9 + */ + +public interface Taglet { + + /** + * Returns the set of locations in which a taglet may be used. + * @return the set of locations in which a taglet may be used + * allowed in or an empty set. + */ + Set getAllowedLocations(); + + /** + * Indicates the tag is an inline or a body tag. + * @return true if this Taglet + * is an inline tag, false otherwise. + */ + public abstract boolean isInlineTag(); + + /** + * Returns the name of the tag. + * @return the name of this custom tag. + */ + public abstract String getName(); + + /** + * Given the {@link DocTree DocTree} representation of this custom + * tag, return its string representation, which is output + * to the generated page. + * @param tag the Tag representation of this custom tag. + * @return the string representation of this Tag. + */ + public abstract String toString(DocTree tag); + + /** + * Given a List of {@link DocTree DocTrees} representing this custom + * tag, return its string representation, which is output + * to the generated page. This method should + * return null if this taglet represents an inline or body tag. + * @param tags the list of DocTrees representing this custom tag. + * @return the string representation of this Tag. + */ + public abstract String toString(List tags); + + /** + * The kind of location. + */ + public static enum Location { + /** In an Overview document. */ + OVERVIEW, + /** In the documentation for a package. */ + PACKAGE, + /** In the documentation for a class, interface or enum. */ + TYPE, + /** In the documentation for a constructor. */ + CONSTRUCTOR, + /** In the documentation for a method. */ + METHOD, + /** In the documentation for a field. */ + FIELD + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/doclet/taglet/package-info.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/doclet/taglet/package-info.java new file mode 100644 index 00000000000..066f5e61472 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/doclet/taglet/package-info.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * The Taglet API provides a way to declare custom tags that can be + * used by the standard doclet. + * + *

+ * Note: The declarations in this package supersede those + * in the older package {@code com.sun.tools.doclets}. + *

+ * + * @since 9 + */ +package jdk.javadoc.doclet.taglet; diff --git a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/api/JavadocTaskImpl.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/api/JavadocTaskImpl.java similarity index 95% rename from langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/api/JavadocTaskImpl.java rename to langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/api/JavadocTaskImpl.java index 18d24b1e6b5..97ec41633c9 100644 --- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/api/JavadocTaskImpl.java +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/api/JavadocTaskImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,21 +23,20 @@ * questions. */ -package com.sun.tools.javadoc.api; +package jdk.javadoc.internal.api; -import com.sun.tools.javac.util.ClientCodeException; +import java.util.Collections; import java.util.Locale; import java.util.concurrent.atomic.AtomicBoolean; import javax.tools.DocumentationTool.DocumentationTask; import javax.tools.JavaFileObject; +import com.sun.tools.javac.util.ClientCodeException; import com.sun.tools.javac.util.Context; -import com.sun.tools.javadoc.Start; -import java.util.Collections; - import com.sun.tools.javac.util.DefinedBy; import com.sun.tools.javac.util.DefinedBy.Api; +import jdk.javadoc.internal.tool.Start; /** * Provides access to functionality specific to the JDK documentation tool, diff --git a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/api/JavadocTool.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/api/JavadocTool.java similarity index 94% rename from langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/api/JavadocTool.java rename to langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/api/JavadocTool.java index 2aa5c1691dd..8b91c15df2e 100644 --- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/api/JavadocTool.java +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/api/JavadocTool.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ * questions. */ -package com.sun.tools.javadoc.api; +package jdk.javadoc.internal.api; import java.io.InputStream; import java.io.OutputStream; @@ -52,7 +52,7 @@ import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.DefinedBy; import com.sun.tools.javac.util.DefinedBy.Api; import com.sun.tools.javac.util.Log; -import com.sun.tools.javadoc.ToolOption; +import jdk.javadoc.internal.tool.ToolOption; /** * Provides access to functionality specific to the JDK documentation tool, @@ -150,10 +150,7 @@ public class JavadocTool implements DocumentationTool { PrintWriter err_pw = new PrintWriter(err == null ? System.err : err, true); PrintWriter out_pw = new PrintWriter(out == null ? System.out : out); try { - String standardDocletName = "com.sun.tools.doclets.standard.Standard"; - ClassLoader cl = getClass().getClassLoader(); - return com.sun.tools.javadoc.Main.execute( - "javadoc", err_pw, err_pw, out_pw, standardDocletName, cl, arguments); + return jdk.javadoc.internal.tool.Main.execute(arguments, err_pw); } finally { err_pw.flush(); out_pw.flush(); diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AbstractExecutableMemberWriter.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AbstractExecutableMemberWriter.java new file mode 100644 index 00000000000..d82da4653cd --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AbstractExecutableMemberWriter.java @@ -0,0 +1,352 @@ +/* + * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.util.SimpleTypeVisitor9; + +import com.sun.tools.javac.util.DefinedBy; +import com.sun.tools.javac.util.DefinedBy.Api; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.DocletConstants; + +import static jdk.javadoc.internal.doclets.formats.html.LinkInfoImpl.Kind.*; + +/** + * Print method and constructor info. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Robert Field + * @author Atul M Dambalkar + * @author Bhavesh Patel (Modified) + */ +public abstract class AbstractExecutableMemberWriter extends AbstractMemberWriter { + + public AbstractExecutableMemberWriter(SubWriterHolderWriter writer, TypeElement typeElement) { + super(writer, typeElement); + } + + public AbstractExecutableMemberWriter(SubWriterHolderWriter writer) { + super(writer); + } + + /** + * Add the type parameters for the executable member. + * + * @param member the member to write type parameters for. + * @param htmltree the content tree to which the parameters will be added. + */ + protected void addTypeParameters(ExecutableElement member, Content htmltree) { + Content typeParameters = getTypeParameters(member); + if (!typeParameters.isEmpty()) { + htmltree.addContent(typeParameters); + htmltree.addContent(writer.getSpace()); + } + } + + /** + * Get the type parameters for the executable member. + * + * @param member the member for which to get the type parameters. + * @return the type parameters. + */ + protected Content getTypeParameters(ExecutableElement member) { + LinkInfoImpl linkInfo = new LinkInfoImpl(configuration, MEMBER_TYPE_PARAMS, member); + return writer.getTypeParameterLinks(linkInfo); + } + + /** + * {@inheritDoc} + */ + @Override + protected Content getDeprecatedLink(Element member) { + StringBuilder sb = new StringBuilder(); + sb.append(utils.getFullyQualifiedName(member)); + if (!utils.isConstructor(member)) { + sb.append("."); + sb.append(member.getSimpleName().toString()); + } + sb.append(utils.flatSignature((ExecutableElement) member)); + + return writer.getDocLink(MEMBER, member, sb.toString()); + } + + /** + * Add the summary link for the member. + * + * @param context the id of the context where the link will be printed + * @param te the classDoc that we should link to + * @param member the member being linked to + * @param tdSummary the content tree to which the link will be added + */ + @Override + protected void addSummaryLink(LinkInfoImpl.Kind context, TypeElement te, Element member, + Content tdSummary) { + ExecutableElement ee = (ExecutableElement)member; + Content memberLink = HtmlTree.SPAN(HtmlStyle.memberNameLink, + writer.getDocLink(context, te, ee, + name(ee), false)); + Content code = HtmlTree.CODE(memberLink); + addParameters(ee, false, code, name(ee).length() - 1); + tdSummary.addContent(code); + } + + /** + * Add the inherited summary link for the member. + * + * @param te the type element that we should link to + * @param member the member being linked to + * @param linksTree the content tree to which the link will be added + */ + @Override + protected void addInheritedSummaryLink(TypeElement te, Element member, Content linksTree) { + linksTree.addContent(writer.getDocLink(MEMBER, te, member, name(member), false)); + } + + /** + * Add the parameter for the executable member. + * + * @param member the member to write parameter for. + * @param param the parameter that needs to be written. + * @param isVarArg true if this is a link to var arg. + * @param tree the content tree to which the parameter information will be added. + */ + protected void addParam(ExecutableElement member, VariableElement param, + boolean isVarArg, Content tree) { + Content link = writer.getLink(new LinkInfoImpl(configuration, EXECUTABLE_MEMBER_PARAM, + param.asType()).varargs(isVarArg)); + tree.addContent(link); + if(name(param).length() > 0) { + tree.addContent(writer.getSpace()); + tree.addContent(name(param)); + } + } + + /** + * Add the receiver annotations information. + * + * @param member the member to write receiver annotations for. + * @param rcvrType the receiver type. + * @param descList list of annotation description. + * @param tree the content tree to which the information will be added. + */ + protected void addReceiverAnnotations(ExecutableElement member, TypeMirror rcvrType, + List annotationMirrors, Content tree) { + writer.addReceiverAnnotationInfo(member, rcvrType, annotationMirrors, tree); + tree.addContent(writer.getSpace()); + tree.addContent(utils.getTypeName(rcvrType, false)); + LinkInfoImpl linkInfo = new LinkInfoImpl(configuration, RECEIVER_TYPE, rcvrType); + tree.addContent(writer.getTypeParameterLinks(linkInfo)); + tree.addContent(writer.getSpace()); + tree.addContent("this"); + } + + + /** + * Add all the parameters for the executable member. + * + * @param member the member to write parameters for. + * @param htmltree the content tree to which the parameters information will be added. + */ + protected void addParameters(ExecutableElement member, Content htmltree, int indentSize) { + addParameters(member, true, htmltree, indentSize); + } + + /** + * Add all the parameters for the executable member. + * + * @param member the member to write parameters for. + * @param includeAnnotations true if annotation information needs to be added. + * @param htmltree the content tree to which the parameters information will be added. + */ + protected void addParameters(ExecutableElement member, + boolean includeAnnotations, Content htmltree, int indentSize) { + htmltree.addContent("("); + String sep = ""; + List parameters = member.getParameters(); + String indent = makeSpace(indentSize + 1); + TypeMirror rcvrType = member.getReceiverType(); + if (includeAnnotations && rcvrType != null && utils.isAnnotated(rcvrType)) { + List annotationMirrors = rcvrType.getAnnotationMirrors(); + addReceiverAnnotations(member, rcvrType, annotationMirrors, htmltree); + sep = "," + DocletConstants.NL + indent; + } + int paramstart; + for (paramstart = 0; paramstart < parameters.size(); paramstart++) { + htmltree.addContent(sep); + VariableElement param = parameters.get(paramstart); + + if (param.getKind() != ElementKind.INSTANCE_INIT) { + if (includeAnnotations) { + boolean foundAnnotations = + writer.addAnnotationInfo(indent.length(), + member, param, htmltree); + if (foundAnnotations) { + htmltree.addContent(DocletConstants.NL); + htmltree.addContent(indent); + } + } + addParam(member, param, + (paramstart == parameters.size() - 1) && member.isVarArgs(), htmltree); + break; + } + } + + for (int i = paramstart + 1; i < parameters.size(); i++) { + htmltree.addContent(","); + htmltree.addContent(DocletConstants.NL); + htmltree.addContent(indent); + if (includeAnnotations) { + boolean foundAnnotations = + writer.addAnnotationInfo(indent.length(), member, parameters.get(i), + htmltree); + if (foundAnnotations) { + htmltree.addContent(DocletConstants.NL); + htmltree.addContent(indent); + } + } + addParam(member, parameters.get(i), (i == parameters.size() - 1) && member.isVarArgs(), + htmltree); + } + htmltree.addContent(")"); + } + + /** + * Add exceptions for the executable member. + * + * @param member the member to write exceptions for. + * @param htmltree the content tree to which the exceptions information will be added. + */ + protected void addExceptions(ExecutableElement member, Content htmltree, int indentSize) { + List exceptions = member.getThrownTypes(); + if (!exceptions.isEmpty()) { + String indent = makeSpace(indentSize + 1 - 7); + htmltree.addContent(DocletConstants.NL); + htmltree.addContent(indent); + htmltree.addContent("throws "); + indent = makeSpace(indentSize + 1); + Content link = writer.getLink(new LinkInfoImpl(configuration, MEMBER, exceptions.get(0))); + htmltree.addContent(link); + for(int i = 1; i < exceptions.size(); i++) { + htmltree.addContent(","); + htmltree.addContent(DocletConstants.NL); + htmltree.addContent(indent); + Content exceptionLink = writer.getLink(new LinkInfoImpl(configuration, MEMBER, + exceptions.get(i))); + htmltree.addContent(exceptionLink); + } + } + } + + protected TypeElement implementsMethodInIntfac(ExecutableElement method, + List intfacs) { + for (TypeElement intf : intfacs) { + List methods = utils.getMethods(intf); + if (!methods.isEmpty()) { + for (ExecutableElement md : methods) { + if (name(md).equals(name(method)) && + md.toString().equals(method.toString())) { + return intf; + } + } + } + } + return null; + } + + /** + * For backward compatibility, include an anchor using the erasures of the + * parameters. NOTE: We won't need this method anymore after we fix + * see tags so that they use the type instead of the erasure. + * + * @param executableElement the ExecutableElement to anchor to. + * @return the 1.4.x style anchor for the executable element. + */ + protected String getErasureAnchor(ExecutableElement executableElement) { + final StringBuilder buf = new StringBuilder(name(executableElement) + "("); + List parameters = executableElement.getParameters(); + boolean foundTypeVariable = false; + for (int i = 0; i < parameters.size(); i++) { + if (i > 0) { + buf.append(","); + } + TypeMirror t = parameters.get(i).asType(); + SimpleTypeVisitor9 stv = new SimpleTypeVisitor9() { + boolean foundTypeVariable = false; + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Boolean visitArray(ArrayType t, Void p) { + visit(t.getComponentType()); + buf.append(utils.getDimension(t)); + return foundTypeVariable; + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Boolean visitTypeVariable(TypeVariable t, Void p) { + buf.append(utils.asTypeElement(t).getQualifiedName()); + foundTypeVariable = true; + return foundTypeVariable; + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Boolean visitDeclared(DeclaredType t, Void p) { + buf.append(utils.getQualifiedTypeName(t)); + return foundTypeVariable; + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + protected Boolean defaultAction(TypeMirror e, Void p) { + buf.append(e.toString()); + return foundTypeVariable; + } + }; + + boolean isTypeVariable = stv.visit(t); + if (!foundTypeVariable) { + foundTypeVariable = isTypeVariable; + } + } + buf.append(")"); + return foundTypeVariable ? writer.getName(buf.toString()) : null; + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AbstractIndexWriter.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AbstractIndexWriter.java new file mode 100644 index 00000000000..b9d4d36c2a5 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AbstractIndexWriter.java @@ -0,0 +1,461 @@ +/* + * Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; +import java.nio.file.*; +import java.util.*; +import java.util.zip.*; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.SimpleElementVisitor9; + +import com.sun.source.doctree.DocTree; +import com.sun.tools.javac.util.DefinedBy; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.DocFile; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; +import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; +import jdk.javadoc.internal.doclets.toolkit.util.IndexBuilder; + +/** + * Generate Index for all the Member Names with Indexing in + * Unicode Order. This class is a base class for {@link SingleIndexWriter} and + * {@link SplitIndexWriter}. It uses the functionality from + * {@link HtmlDocletWriter} to generate the Index Contents. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @see IndexBuilder + * @author Atul M Dambalkar + * @author Bhavesh Patel (Modified) + */ +public class AbstractIndexWriter extends HtmlDocletWriter { + + /** + * The index of all the members with unicode character. + */ + protected IndexBuilder indexbuilder; + + /** + * This constructor will be used by {@link SplitIndexWriter}. Initializes + * path to this file and relative path from this file. + * + * @param configuration The current configuration + * @param path Path to the file which is getting generated. + * @param indexbuilder Unicode based Index from {@link IndexBuilder} + */ + protected AbstractIndexWriter(ConfigurationImpl configuration, + DocPath path, + IndexBuilder indexbuilder) + throws IOException { + super(configuration, path); + this.indexbuilder = indexbuilder; + } + + /** + * Get the index label for navigation bar. + * + * @return a content tree for the tree label + */ + protected Content getNavLinkIndex() { + Content li = HtmlTree.LI(HtmlStyle.navBarCell1Rev, indexLabel); + return li; + } + + /** + * Add the member information for the unicode character along with the + * list of the members. + * + * @param uc Unicode for which member list information to be generated + * @param memberlist List of members for the unicode character + * @param contentTree the content tree to which the information will be added + */ + protected void addContents(Character uc, Collection memberlist, + Content contentTree) { + addHeading(uc, contentTree); + // Display the list only if there are elements to be displayed. + if (!memberlist.isEmpty()) { + Content dl = new HtmlTree(HtmlTag.DL); + for (Element element : memberlist) { + addDescription(dl, element); + } + contentTree.addContent(dl); + } + } + + protected void addSearchContents(Character uc, List searchList, + Content contentTree) { + addHeading(uc, contentTree); + // Display the list only if there are elements to be displayed. + if (!searchList.isEmpty()) { + Content dl = new HtmlTree(HtmlTag.DL); + for (SearchIndexItem sii : searchList) { + addDescription(sii, dl); + } + contentTree.addContent(dl); + } + } + + protected void addContents(Character uc, List memberlist, + List searchList, Content contentTree) { + addHeading(uc, contentTree); + int memberListSize = memberlist.size(); + int searchListSize = searchList.size(); + int i = 0; + int j = 0; + Content dl = new HtmlTree(HtmlTag.DL); + while (i < memberListSize && j < searchListSize) { + String name = utils.getSimpleName(memberlist.get(i)); + if (name.compareTo(searchList.get(j).getLabel()) < 0) { + addDescription(dl, memberlist.get(i)); + i++; + } else if (name.compareTo(searchList.get(j).getLabel()) > 0) { + addDescription(searchList.get(j), dl); + j++; + } else { + addDescription(dl, memberlist.get(i)); + addDescription(searchList.get(j), dl); + j++; + i++; + } + } + if (i >= memberListSize) { + while (j < searchListSize) { + addDescription(searchList.get(j), dl); + j++; + } + } + if (j >= searchListSize) { + while (i < memberListSize) { + addDescription(dl, memberlist.get(i)); + i++; + } + } + contentTree.addContent(dl); + } + + protected void addHeading(Character uc, Content contentTree) { + String unicode = uc.toString(); + contentTree.addContent(getMarkerAnchorForIndex(unicode)); + Content headContent = new StringContent(unicode); + Content heading = HtmlTree.HEADING(HtmlConstants.CONTENT_HEADING, false, + HtmlStyle.title, headContent); + contentTree.addContent(heading); + } + + protected void addDescription(Content dl, Element element) { + SearchIndexItem si = new SearchIndexItem(); + new SimpleElementVisitor9() { + + @Override @DefinedBy(DefinedBy.Api.LANGUAGE_MODEL) + public Void visitPackage(PackageElement e, Void p) { + addDescription(e, dl, si); + configuration.packageSearchIndex.add(si); + return null; + } + + @Override @DefinedBy(DefinedBy.Api.LANGUAGE_MODEL) + public Void visitType(TypeElement e, Void p) { + addDescription(e, dl, si); + configuration.typeSearchIndex.add(si); + return null; + } + + @Override @DefinedBy(DefinedBy.Api.LANGUAGE_MODEL) + protected Void defaultAction(Element e, Void p) { + addDescription(e, dl, si); + configuration.memberSearchIndex.add(si); + return null; + } + + }.visit(element); + } + + /** + * Add one line summary comment for the package. + * + * @param pkg the package to be documented + * @param dlTree the content tree to which the description will be added + */ + protected void addDescription(PackageElement pkg, Content dlTree, SearchIndexItem si) { + Content link = getPackageLink(pkg, new StringContent(utils.getPackageName(pkg))); + si.setLabel(utils.getPackageName(pkg)); + si.setCategory(getResource("doclet.Packages").toString()); + Content dt = HtmlTree.DT(link); + dt.addContent(" - "); + dt.addContent(getResource("doclet.package")); + dt.addContent(" " + utils.getPackageName(pkg)); + dlTree.addContent(dt); + Content dd = new HtmlTree(HtmlTag.DD); + addSummaryComment(pkg, dd); + dlTree.addContent(dd); + } + + /** + * Add one line summary comment for the class. + * + * @param typeElement the class being documented + * @param dlTree the content tree to which the description will be added + */ + protected void addDescription(TypeElement typeElement, Content dlTree, SearchIndexItem si) { + Content link = getLink(new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.INDEX, typeElement).strong(true)); + si.setContainingPackage(utils.getPackageName(utils.containingPackage(typeElement))); + si.setLabel(utils.getSimpleName(typeElement)); + si.setCategory(getResource("doclet.Types").toString()); + Content dt = HtmlTree.DT(link); + dt.addContent(" - "); + addClassInfo(typeElement, dt); + dlTree.addContent(dt); + Content dd = new HtmlTree(HtmlTag.DD); + addComment(typeElement, dd); + dlTree.addContent(dd); + } + + /** + * Add the classkind (class, interface, exception), error of the class + * passed. + * + * @param te the class being documented + * @param contentTree the content tree to which the class info will be added + */ + protected void addClassInfo(TypeElement te, Content contentTree) { + contentTree.addContent(getResource("doclet.in", + utils.getTypeElementName(te, false), + getPackageLink(utils.containingPackage(te), + utils.getPackageName(utils.containingPackage(te))) + )); + } + + /** + * Add description for Class, Field, Method or Constructor. + * + * @param member the member of the Class Kind + * @param dlTree the content tree to which the description will be added + * @param si search index item + */ + protected void addDescription(Element member, Content dlTree, SearchIndexItem si) { + + si.setContainingPackage(utils.getPackageName(utils.containingPackage(member))); + si.setContainingClass(utils.getSimpleName(utils.getEnclosingTypeElement(member))); + String name = utils.getSimpleName(member); + if (utils.isExecutableElement(member)) { + ExecutableElement ee = (ExecutableElement)member; + name = name + utils.flatSignature(ee); + si.setLabel(name); + if (!((utils.signature(ee)).equals(utils.flatSignature(ee)))) { + si.setUrl(getName(getAnchor(ee))); + } + + } else { + si.setLabel(name); + } + si.setCategory(getResource("doclet.Members").toString()); + Content span = HtmlTree.SPAN(HtmlStyle.memberNameLink, + getDocLink(LinkInfoImpl.Kind.INDEX, member, name)); + Content dt = HtmlTree.DT(span); + dt.addContent(" - "); + addMemberDesc(member, dt); + dlTree.addContent(dt); + Content dd = new HtmlTree(HtmlTag.DD); + addComment(member, dd); + dlTree.addContent(dd); + } + + protected void addDescription(SearchIndexItem sii, Content dlTree) { + String path = pathToRoot.isEmpty() ? "" : pathToRoot.getPath() + "/"; + path += sii.getUrl(); + HtmlTree labelLink = HtmlTree.A(path, new StringContent(sii.getLabel())); + Content dt = HtmlTree.DT(HtmlTree.SPAN(HtmlStyle.searchTagLink, labelLink)); + dt.addContent(" - "); + dt.addContent(getResource("doclet.Search_tag_in", sii.getHolder())); + dlTree.addContent(dt); + Content dd = new HtmlTree(HtmlTag.DD); + if (sii.getDescription().isEmpty()) { + dd.addContent(getSpace()); + } else { + dd.addContent(sii.getDescription()); + } + dlTree.addContent(dd); + } + + /** + * Add comment for each element in the index. If the element is deprecated + * and it has a @deprecated tag, use that comment. Else if the containing + * class for this element is deprecated, then add the word "Deprecated." at + * the start and then print the normal comment. + * + * @param element Index element + * @param contentTree the content tree to which the comment will be added + */ + protected void addComment(Element element, Content contentTree) { + List tags; + Content span = HtmlTree.SPAN(HtmlStyle.deprecatedLabel, deprecatedPhrase); + HtmlTree div = new HtmlTree(HtmlTag.DIV); + div.addStyle(HtmlStyle.block); + if (utils.isDeprecated(element)) { + div.addContent(span); + tags = utils.getBlockTags(element, DocTree.Kind.DEPRECATED); + if (!tags.isEmpty()) + addInlineDeprecatedComment(element, tags.get(0), div); + contentTree.addContent(div); + } else { + TypeElement encl = utils.getEnclosingTypeElement(element); + while (encl != null) { + if (utils.isDeprecated(encl)) { + div.addContent(span); + contentTree.addContent(div); + break; + } + encl = utils.getEnclosingTypeElement(encl); + } + addSummaryComment(element, contentTree); + } + } + + /** + * Add description about the Static Variable/Method/Constructor for a + * member. + * + * @param member MemberDoc for the member within the Class Kind + * @param contentTree the content tree to which the member description will be added + */ + protected void addMemberDesc(Element member, Content contentTree) { + TypeElement containing = utils.getEnclosingTypeElement(member); + String classdesc = utils.getTypeElementName(containing, true) + " "; + if (utils.isField(member)) { + Content resource = getResource(utils.isStatic(member) + ? "doclet.Static_variable_in" + : "doclet.Variable_in", classdesc); + contentTree.addContent(resource); + } else if (utils.isConstructor(member)) { + contentTree.addContent( + getResource("doclet.Constructor_for", classdesc)); + } else if (utils.isMethod(member)) { + Content resource = getResource(utils.isStatic(member) + ? "doclet.Static_method_in" + : "doclet.Method_in", classdesc); + contentTree.addContent(resource); + } + addPreQualifiedClassLink(LinkInfoImpl.Kind.INDEX, containing, + false, contentTree); + } + + /** + * Get the marker anchor which will be added to the index documentation tree. + * + * @param anchorNameForIndex the anchor name attribute for index page + * @return a content tree for the marker anchor + */ + public Content getMarkerAnchorForIndex(String anchorNameForIndex) { + return getMarkerAnchor(getNameForIndex(anchorNameForIndex), null); + } + + /** + * Generate a valid HTML name for member index page. + * + * @param unicode the string that needs to be converted to valid HTML name. + * @return a valid HTML name string. + */ + public String getNameForIndex(String unicode) { + return "I:" + getName(unicode); + } + + protected void createSearchIndexFiles() { + createSearchIndexFile(DocPaths.PACKAGE_SEARCH_INDEX_JSON, DocPaths.PACKAGE_SEARCH_INDEX_ZIP, + configuration.packageSearchIndex); + createSearchIndexFile(DocPaths.TYPE_SEARCH_INDEX_JSON, DocPaths.TYPE_SEARCH_INDEX_ZIP, + configuration.typeSearchIndex); + createSearchIndexFile(DocPaths.MEMBER_SEARCH_INDEX_JSON, DocPaths.MEMBER_SEARCH_INDEX_ZIP, + configuration.memberSearchIndex); + createSearchIndexFile(DocPaths.TAG_SEARCH_INDEX_JSON, DocPaths.TAG_SEARCH_INDEX_ZIP, + configuration.tagSearchIndex); + } + + protected void createSearchIndexFile(DocPath searchIndexFile, DocPath searchIndexZip, + List searchIndex) { + if (!searchIndex.isEmpty()) { + try { + StringBuilder searchVar = new StringBuilder("["); + boolean first = true; + DocFile searchFile = DocFile.createFileForOutput(configuration, searchIndexFile); + Path p = Paths.get(searchFile.getPath()); + for (SearchIndexItem item : searchIndex) { + if (first) { + searchVar.append(item.toString()); + first = false; + } else { + searchVar.append(",").append(item.toString()); + } + } + searchVar.append("]"); + Files.write(p, searchVar.toString().getBytes()); + DocFile zipFile = DocFile.createFileForOutput(configuration, searchIndexZip); + try (FileOutputStream fos = new FileOutputStream(zipFile.getPath()); + ZipOutputStream zos = new ZipOutputStream(fos)) { + zipFile(searchFile.getPath(), searchIndexFile, zos); + } + Files.delete(p); + } catch (IOException ie) { + throw new DocletAbortException(ie); + } + } + } + + protected void zipFile(String inputFile, DocPath file, ZipOutputStream zos) { + try { + try { + ZipEntry ze = new ZipEntry(file.getPath()); + zos.putNextEntry(ze); + try (FileInputStream fis = new FileInputStream(new File(inputFile))) { + byte[] buf = new byte[2048]; + int len = fis.read(buf); + while (len > 0) { + zos.write(buf, 0, len); + len = fis.read(buf); + } + } + } finally { + zos.closeEntry(); + } + } catch (IOException e) { + throw new DocletAbortException(e); + } + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AbstractMemberWriter.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AbstractMemberWriter.java new file mode 100644 index 00000000000..c963b909a52 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AbstractMemberWriter.java @@ -0,0 +1,708 @@ +/* + * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.util.*; +import java.util.stream.Collectors; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.SimpleElementVisitor9; + +import com.sun.source.doctree.DocTree; +import com.sun.tools.javac.util.DefinedBy; +import com.sun.tools.javac.util.DefinedBy.Api; + +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlAttr; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.taglets.DeprecatedTaglet; +import jdk.javadoc.internal.doclets.toolkit.util.MethodTypes; +import jdk.javadoc.internal.doclets.toolkit.util.Utils; +import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberMap; + +import static javax.lang.model.element.Modifier.*; + +/** + * The base class for member writers. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Robert Field + * @author Atul M Dambalkar + * @author Jamie Ho (Re-write) + * @author Bhavesh Patel (Modified) + */ +public abstract class AbstractMemberWriter { + + protected final ConfigurationImpl configuration; + protected final Utils utils; + protected final SubWriterHolderWriter writer; + protected final TypeElement typeElement; + protected Map typeMap = new LinkedHashMap<>(); + protected Set methodTypes = EnumSet.noneOf(MethodTypes.class); + private int methodTypesOr = 0; + public final boolean nodepr; + + protected boolean printedSummaryHeader = false; + + public AbstractMemberWriter(SubWriterHolderWriter writer, TypeElement typeElement) { + this.configuration = writer.configuration; + this.writer = writer; + this.nodepr = configuration.nodeprecated; + this.typeElement = typeElement; + this.utils = writer.configuration.utils; + } + + public AbstractMemberWriter(SubWriterHolderWriter writer) { + this(writer, null); + } + + /*** abstracts ***/ + + /** + * Add the summary label for the member. + * + * @param memberTree the content tree to which the label will be added + */ + public abstract void addSummaryLabel(Content memberTree); + + /** + * Get the summary for the member summary table. + * + * @return a string for the table summary + */ + public abstract String getTableSummary(); + + /** + * Get the caption for the member summary table. + * + * @return a string for the table caption + */ + public abstract Content getCaption(); + + /** + * Get the summary table header for the member. + * + * @param member the member to be documented + * @return the summary table header + */ + public abstract List getSummaryTableHeader(Element member); + + /** + * Add inherited summary label for the member. + * + * @param typeElement the TypeElement to which to link to + * @param inheritedTree the content tree to which the inherited summary label will be added + */ + public abstract void addInheritedSummaryLabel(TypeElement typeElement, Content inheritedTree); + + /** + * Add the anchor for the summary section of the member. + * + * @param typeElement the TypeElement to be documented + * @param memberTree the content tree to which the summary anchor will be added + */ + public abstract void addSummaryAnchor(TypeElement typeElement, Content memberTree); + + /** + * Add the anchor for the inherited summary section of the member. + * + * @param typeElement the TypeElement to be documented + * @param inheritedTree the content tree to which the inherited summary anchor will be added + */ + public abstract void addInheritedSummaryAnchor(TypeElement typeElement, Content inheritedTree); + + /** + * Add the summary type for the member. + * + * @param member the member to be documented + * @param tdSummaryType the content tree to which the type will be added + */ + protected abstract void addSummaryType(Element member, Content tdSummaryType); + + /** + * Add the summary link for the member. + * + * @param typeElement the TypeElement to be documented + * @param member the member to be documented + * @param tdSummary the content tree to which the link will be added + */ + protected void addSummaryLink(TypeElement typeElement, Element member, Content tdSummary) { + addSummaryLink(LinkInfoImpl.Kind.MEMBER, typeElement, member, tdSummary); + } + + /** + * Add the summary link for the member. + * + * @param context the id of the context where the link will be printed + * @param typeElement the TypeElement to be documented + * @param member the member to be documented + * @param tdSummary the content tree to which the summary link will be added + */ + protected abstract void addSummaryLink(LinkInfoImpl.Kind context, + TypeElement typeElement, Element member, Content tdSummary); + + /** + * Add the inherited summary link for the member. + * + * @param typeElement the TypeElement to be documented + * @param member the member to be documented + * @param linksTree the content tree to which the inherited summary link will be added + */ + protected abstract void addInheritedSummaryLink(TypeElement typeElement, + Element member, Content linksTree); + + /** + * Get the deprecated link. + * + * @param member the member being linked to + * @return a content tree representing the link + */ + protected abstract Content getDeprecatedLink(Element member); + + /** + * Get the navigation summary link. + * + * @param typeElement the TypeElement to be documented + * @param link true if its a link else the label to be printed + * @return a content tree for the navigation summary link. + */ + protected abstract Content getNavSummaryLink(TypeElement typeElement, boolean link); + + /** + * Add the navigation detail link. + * + * @param link true if its a link else the label to be printed + * @param liNav the content tree to which the navigation detail link will be added + */ + protected abstract void addNavDetailLink(boolean link, Content liNav); + + /** + * Add the member name to the content tree. + * + * @param name the member name to be added to the content tree. + * @param htmltree the content tree to which the name will be added. + */ + protected void addName(String name, Content htmltree) { + htmltree.addContent(name); + } + + protected String typeString(Element member) { + return new SimpleElementVisitor9() { + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public String visitExecutable(ExecutableElement e, Void p) { + return utils.isMethod(e) ? e.getReturnType().toString() : ""; + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public String visitVariable(VariableElement e, Void p) { + return e.toString(); + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + protected String defaultAction(Element e, Void p) { + return ""; + } + }.visit(member); + } + + /** + * Add the modifier for the member. The modifiers are ordered as specified + * by The Java Language Specification. + * + * @param member the member for which teh modifier will be added. + * @param htmltree the content tree to which the modifier information will be added. + */ + protected void addModifiers(Element member, Content htmltree) { + Set set = new TreeSet<>(member.getModifiers()); + + // remove the ones we really don't need + set.remove(NATIVE); + set.remove(SYNCHRONIZED); + set.remove(STRICTFP); + + // According to JLS, we should not be showing public modifier for + // interface methods. + if ((utils.isField(member) || utils.isMethod(member)) + && writer instanceof ClassWriterImpl + && utils.isInterface(((ClassWriterImpl) writer).getTypeElement())) { + // Remove the implicit abstract and public modifiers + if (utils.isMethod(member) && utils.isInterface(member.getEnclosingElement())) { + set.remove(ABSTRACT); + set.remove(PUBLIC); + } + if (!utils.isMethod(member)) { + set.remove(PUBLIC); + } + } + if (!set.isEmpty()) { + String mods = set.stream().map(m -> m.toString()).collect(Collectors.joining(" ")); + htmltree.addContent(mods); + htmltree.addContent(writer.getSpace()); + } + } + + protected String makeSpace(int len) { + if (len <= 0) { + return ""; + } + StringBuilder sb = new StringBuilder(len); + for (int i = 0; i < len; i++) { + sb.append(' '); + } + return sb.toString(); + } + + /** + * Add the modifier and type for the member in the member summary. + * + * @param member the member to add the type for + * @param type the type to add + * @param tdSummaryType the content tree to which the modified and type will be added + */ + protected void addModifierAndType(Element member, TypeMirror type, + Content tdSummaryType) { + HtmlTree code = new HtmlTree(HtmlTag.CODE); + addModifier(member, code); + if (type == null) { + code.addContent(utils.isClass(member) ? "class" : "interface"); + code.addContent(writer.getSpace()); + } else { + List list = utils.isExecutableElement(member) + ? ((ExecutableElement)member).getTypeParameters() + : null; + if (list != null && !list.isEmpty()) { + Content typeParameters = ((AbstractExecutableMemberWriter) this) + .getTypeParameters((ExecutableElement)member); + code.addContent(typeParameters); + //Code to avoid ugly wrapping in member summary table. + if (typeParameters.charCount() > 10) { + code.addContent(new HtmlTree(HtmlTag.BR)); + } else { + code.addContent(writer.getSpace()); + } + code.addContent( + writer.getLink(new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.SUMMARY_RETURN_TYPE, type))); + } else { + code.addContent( + writer.getLink(new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.SUMMARY_RETURN_TYPE, type))); + } + + } + tdSummaryType.addContent(code); + } + + /** + * Add the modifier for the member. + * + * @param member the member to add the type for + * @param code the content tree to which the modified will be added + */ + private void addModifier(Element member, Content code) { + if (utils.isProtected(member)) { + code.addContent("protected "); + } else if (utils.isPrivate(member)) { + code.addContent("private "); + } else if (!utils.isPublic(member)) { // Package private + code.addContent(configuration.getText("doclet.Package_private")); + code.addContent(" "); + } + boolean isAnnotatedTypeElement = utils.isAnnotationType(member.getEnclosingElement()); + if (!isAnnotatedTypeElement && utils.isMethod(member)) { + if (!utils.isInterface(member.getEnclosingElement()) && utils.isAbstract(member)) { + code.addContent("abstract "); + } + if (utils.isDefault(member)) { + code.addContent("default "); + } + } + if (utils.isStatic(member)) { + code.addContent("static "); + } + } + + /** + * Add the deprecated information for the given member. + * + * @param member the member being documented. + * @param contentTree the content tree to which the deprecated information will be added. + */ + protected void addDeprecatedInfo(Element member, Content contentTree) { + Content output = (new DeprecatedTaglet()).getTagletOutput(member, + writer.getTagletWriterInstance(false)); + if (!output.isEmpty()) { + Content deprecatedContent = output; + Content div = HtmlTree.DIV(HtmlStyle.block, deprecatedContent); + contentTree.addContent(div); + } + } + + /** + * Add the comment for the given member. + * + * @param member the member being documented. + * @param htmltree the content tree to which the comment will be added. + */ + protected void addComment(Element member, Content htmltree) { + if (!utils.getBody(member).isEmpty()) { + writer.addInlineComment(member, htmltree); + } + } + + protected String name(Element member) { + return utils.getSimpleName(member); + } + + /** + * Get the header for the section. + * + * @param member the member being documented. + * @return a header content for the section. + */ + protected Content getHead(Element member) { + Content memberContent = new StringContent(name(member)); + Content heading = HtmlTree.HEADING(HtmlConstants.MEMBER_HEADING, memberContent); + return heading; + } + + /** + * Return true if the given ProgramElement is inherited + * by the class that is being documented. + * + * @param ped The ProgramElement being checked. + * return true if the ProgramElement is being inherited and + * false otherwise. + */ + protected boolean isInherited(Element ped){ + return (!utils.isPrivate(ped) && + (!utils.isPackagePrivate(ped) || + ped.getEnclosingElement().equals(ped.getEnclosingElement()))); + } + + /** + * Add deprecated information to the documentation tree + * + * @param deprmembers list of deprecated members + * @param headingKey the caption for the deprecated members table + * @param tableSummary the summary for the deprecated members table + * @param tableHeader table headers for the deprecated members table + * @param contentTree the content tree to which the deprecated members table will be added + */ + protected void addDeprecatedAPI(Collection deprmembers, String headingKey, + String tableSummary, List tableHeader, Content contentTree) { + if (deprmembers.size() > 0) { + Content caption = writer.getTableCaption(configuration.getResource(headingKey)); + Content table = (configuration.isOutputHtml5()) + ? HtmlTree.TABLE(HtmlStyle.deprecatedSummary, caption) + : HtmlTree.TABLE(HtmlStyle.deprecatedSummary, tableSummary, caption); + table.addContent(writer.getSummaryTableHeader(tableHeader, "col")); + Content tbody = new HtmlTree(HtmlTag.TBODY); + boolean altColor = true; + for (Element member : deprmembers) { + HtmlTree td = HtmlTree.TD(HtmlStyle.colOne, getDeprecatedLink(member)); + List deprTrees = utils.getBlockTags(member, DocTree.Kind.DEPRECATED); + if (!deprTrees.isEmpty()) { + writer.addInlineDeprecatedComment(member, deprTrees.get(0), td); + } + HtmlTree tr = HtmlTree.TR(td); + tr.addStyle(altColor ? HtmlStyle.altColor : HtmlStyle.rowColor); + altColor = !altColor; + tbody.addContent(tr); + } + table.addContent(tbody); + Content li = HtmlTree.LI(HtmlStyle.blockList, table); + Content ul = HtmlTree.UL(HtmlStyle.blockList, li); + contentTree.addContent(ul); + } + } + + /** + * Add use information to the documentation tree. + * + * @param mems list of program elements for which the use information will be added + * @param heading the section heading + * @param tableSummary the summary for the use table + * @param contentTree the content tree to which the use information will be added + */ + protected void addUseInfo(List mems, + Content heading, String tableSummary, Content contentTree) { + if (mems == null || mems.isEmpty()) { + return; + } + List members = mems; + boolean printedUseTableHeader = false; + if (members.size() > 0) { + Content caption = writer.getTableCaption(heading); + Content table = (configuration.isOutputHtml5()) + ? HtmlTree.TABLE(HtmlStyle.useSummary, caption) + : HtmlTree.TABLE(HtmlStyle.useSummary, tableSummary, caption); + Content tbody = new HtmlTree(HtmlTag.TBODY); + boolean altColor = true; + for (Element element : members) { + TypeElement te = utils.getEnclosingTypeElement(element); + if (!printedUseTableHeader) { + table.addContent(writer.getSummaryTableHeader( + this.getSummaryTableHeader(element), "col")); + printedUseTableHeader = true; + } + HtmlTree tr = new HtmlTree(HtmlTag.TR); + tr.addStyle(altColor ? HtmlStyle.altColor : HtmlStyle.rowColor); + altColor = !altColor; + HtmlTree tdFirst = new HtmlTree(HtmlTag.TD); + tdFirst.addStyle(HtmlStyle.colFirst); + writer.addSummaryType(this, element, tdFirst); + tr.addContent(tdFirst); + HtmlTree tdLast = new HtmlTree(HtmlTag.TD); + tdLast.addStyle(HtmlStyle.colLast); + if (te != null + && !utils.isConstructor(element) + && !utils.isClass(element) + && !utils.isInterface(element) + && !utils.isAnnotationType(element)) { + HtmlTree name = new HtmlTree(HtmlTag.SPAN); + name.addStyle(HtmlStyle.typeNameLabel); + name.addContent(name(te) + "."); + tdLast.addContent(name); + } + addSummaryLink(utils.isClass(element) || utils.isInterface(element) + ? LinkInfoImpl.Kind.CLASS_USE + : LinkInfoImpl.Kind.MEMBER, + te, element, tdLast); + writer.addSummaryLinkComment(this, element, tdLast); + tr.addContent(tdLast); + tbody.addContent(tr); + } + table.addContent(tbody); + contentTree.addContent(table); + } + } + + /** + * Add the navigation detail link. + * + * @param members the members to be linked + * @param liNav the content tree to which the navigation detail link will be added + */ + protected void addNavDetailLink(SortedSet members, Content liNav) { + addNavDetailLink(!members.isEmpty(), liNav); + } + + /** + * Add the navigation summary link. + * + * @param members members to be linked + * @param visibleMemberMap the visible inherited members map + * @param liNav the content tree to which the navigation summary link will be added + */ + protected void addNavSummaryLink(SortedSet members, + VisibleMemberMap visibleMemberMap, Content liNav) { + if (!members.isEmpty()) { + liNav.addContent(getNavSummaryLink(null, true)); + return; + } + + TypeElement superClass = utils.getSuperClass(typeElement); + while (superClass != null) { + if (visibleMemberMap.hasMembersFor(superClass)) { + liNav.addContent(getNavSummaryLink(superClass, true)); + return; + } + superClass = utils.getSuperClass(superClass); + } + liNav.addContent(getNavSummaryLink(null, false)); + } + + protected void serialWarning(Element e, String key, String a1, String a2) { + if (configuration.serialwarn) { + configuration.getDocletSpecificMsg().warning(e, key, a1, a2); + } + } + + /** + * Add the member summary for the given class. + * + * @param tElement the class that is being documented + * @param member the member being documented + * @param firstSentenceTags the first sentence tags to be added to the summary + * @param tableContents the list of contents to which the documentation will be added + * @param counter the counter for determining id and style for the table row + */ + public void addMemberSummary(TypeElement tElement, Element member, + List firstSentenceTags, List tableContents, int counter) { + HtmlTree tdSummaryType = new HtmlTree(HtmlTag.TD); + tdSummaryType.addStyle(HtmlStyle.colFirst); + writer.addSummaryType(this, member, tdSummaryType); + HtmlTree tdSummary = new HtmlTree(HtmlTag.TD); + setSummaryColumnStyle(tdSummary); + addSummaryLink(tElement, member, tdSummary); + writer.addSummaryLinkComment(this, member, firstSentenceTags, tdSummary); + HtmlTree tr = HtmlTree.TR(tdSummaryType); + tr.addContent(tdSummary); + if (utils.isMethod(member) && !utils.isAnnotationType(member)) { + int methodType = utils.isStatic(member) ? MethodTypes.STATIC.value() : + MethodTypes.INSTANCE.value(); + if (utils.isInterface(member.getEnclosingElement())) { + methodType = utils.isAbstract(member) + ? methodType | MethodTypes.ABSTRACT.value() + : methodType | MethodTypes.DEFAULT.value(); + } else { + methodType = utils.isAbstract(member) + ? methodType | MethodTypes.ABSTRACT.value() + : methodType | MethodTypes.CONCRETE.value(); + } + if (utils.isDeprecated(member) || utils.isDeprecated(typeElement)) { + methodType = methodType | MethodTypes.DEPRECATED.value(); + } + methodTypesOr = methodTypesOr | methodType; + String tableId = "i" + counter; + typeMap.put(tableId, methodType); + tr.addAttr(HtmlAttr.ID, tableId); + } + if (counter%2 == 0) + tr.addStyle(HtmlStyle.altColor); + else + tr.addStyle(HtmlStyle.rowColor); + tableContents.add(tr); + } + + /** + * Generate the method types set and return true if the method summary table + * needs to show tabs. + * + * @return true if the table should show tabs + */ + public boolean showTabs() { + int value; + for (MethodTypes type : EnumSet.allOf(MethodTypes.class)) { + value = type.value(); + if ((value & methodTypesOr) == value) { + methodTypes.add(type); + } + } + boolean showTabs = methodTypes.size() > 1; + if (showTabs) { + methodTypes.add(MethodTypes.ALL); + } + return showTabs; + } + + /** + * Set the style for the summary column. + * + * @param tdTree the column for which the style will be set + */ + public void setSummaryColumnStyle(HtmlTree tdTree) { + tdTree.addStyle(HtmlStyle.colLast); + } + + /** + * Add inherited member summary for the given class and member. + * + * @param tElement the class the inherited member belongs to + * @param nestedClass the inherited member that is summarized + * @param isFirst true if this is the first member in the list + * @param isLast true if this is the last member in the list + * @param linksTree the content tree to which the summary will be added + */ + public void addInheritedMemberSummary(TypeElement tElement, + Element nestedClass, boolean isFirst, boolean isLast, + Content linksTree) { + writer.addInheritedMemberSummary(this, tElement, nestedClass, isFirst, + linksTree); + } + + /** + * Get the inherited summary header for the given class. + * + * @param tElement the class the inherited member belongs to + * @return a content tree for the inherited summary header + */ + public Content getInheritedSummaryHeader(TypeElement tElement) { + Content inheritedTree = writer.getMemberTreeHeader(); + writer.addInheritedSummaryHeader(this, tElement, inheritedTree); + return inheritedTree; + } + + /** + * Get the inherited summary links tree. + * + * @return a content tree for the inherited summary links + */ + public Content getInheritedSummaryLinksTree() { + return new HtmlTree(HtmlTag.CODE); + } + + /** + * Get the summary table tree for the given class. + * + * @param tElement the class for which the summary table is generated + * @param tableContents list of contents to be displayed in the summary table + * @return a content tree for the summary table + */ + public Content getSummaryTableTree(TypeElement tElement, List tableContents) { + return writer.getSummaryTableTree(this, tElement, tableContents, showTabs()); + } + + /** + * Get the member tree to be documented. + * + * @param memberTree the content tree of member to be documented + * @return a content tree that will be added to the class documentation + */ + public Content getMemberTree(Content memberTree) { + return writer.getMemberTree(memberTree); + } + + /** + * Get the member tree to be documented. + * + * @param memberTree the content tree of member to be documented + * @param isLastContent true if the content to be added is the last content + * @return a content tree that will be added to the class documentation + */ + public Content getMemberTree(Content memberTree, boolean isLastContent) { + if (isLastContent) + return HtmlTree.UL(HtmlStyle.blockListLast, memberTree); + else + return HtmlTree.UL(HtmlStyle.blockList, memberTree); + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AbstractPackageIndexWriter.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AbstractPackageIndexWriter.java new file mode 100644 index 00000000000..26061a83b8d --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AbstractPackageIndexWriter.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 1998, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; +import java.util.*; + +import javax.lang.model.element.PackageElement; + +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.RawHtml; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; + +/** + * Abstract class to generate the overview files in + * Frame and Non-Frame format. This will be sub-classed by to + * generate overview-frame.html as well as overview-summary.html. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Atul M Dambalkar + * @author Bhavesh Patel (Modified) + */ +public abstract class AbstractPackageIndexWriter extends HtmlDocletWriter { + + /** + * A Set of Packages to be documented. + */ + protected SortedSet packages; + + /** + * Constructor. Also initializes the packages variable. + * + * @param configuration The current configuration + * @param filename Name of the package index file to be generated. + */ + public AbstractPackageIndexWriter(ConfigurationImpl configuration, + DocPath filename) throws IOException { + super(configuration, filename); + packages = configuration.packages; + } + + /** + * Adds the navigation bar header to the documentation tree. + * + * @param body the document tree to which the navigation bar header will be added + */ + protected abstract void addNavigationBarHeader(Content body); + + /** + * Adds the navigation bar footer to the documentation tree. + * + * @param body the document tree to which the navigation bar footer will be added + */ + protected abstract void addNavigationBarFooter(Content body); + + /** + * Adds the overview header to the documentation tree. + * + * @param body the document tree to which the overview header will be added + */ + protected abstract void addOverviewHeader(Content body); + + /** + * Adds the packages list to the documentation tree. + * + * @param packages a collection of packagedoc objects + * @param text caption for the table + * @param tableSummary summary for the table + * @param body the document tree to which the packages list will be added + */ + protected abstract void addPackagesList(Collection packages, String text, + String tableSummary, Content body); + + /** + * Generate and prints the contents in the package index file. Call appropriate + * methods from the sub-class in order to generate Frame or Non + * Frame format. + * + * @param title the title of the window. + * @param includeScript boolean set true if windowtitle script is to be included + */ + protected void buildPackageIndexFile(String title, boolean includeScript) throws IOException { + String windowOverview = configuration.getText(title); + Content body = getBody(includeScript, getWindowTitle(windowOverview)); + addNavigationBarHeader(body); + addOverviewHeader(body); + addIndex(body); + addOverview(body); + addNavigationBarFooter(body); + printHtmlDocument(configuration.metakeywords.getOverviewMetaKeywords(title, + configuration.doctitle), includeScript, body); + } + + /** + * Default to no overview, override to add overview. + * + * @param body the document tree to which the overview will be added + */ + protected void addOverview(Content body) throws IOException { + } + + /** + * Adds the frame or non-frame package index to the documentation tree. + * + * @param body the document tree to which the index will be added + */ + protected void addIndex(Content body) { + addIndexContents(packages, "doclet.Package_Summary", + configuration.getText("doclet.Member_Table_Summary", + configuration.getText("doclet.Package_Summary"), + configuration.getText("doclet.packages")), body); + } + + /** + * Adds package index contents. Call appropriate methods from + * the sub-classes. Adds it to the body HtmlTree + * + * @param packages a collection of packages to be documented + * @param text string which will be used as the heading + * @param tableSummary summary for the table + * @param body the document tree to which the index contents will be added + */ + protected void addIndexContents(Collection packages, String text, + String tableSummary, Content body) { + if (!packages.isEmpty()) { + HtmlTree htmlTree = (configuration.allowTag(HtmlTag.NAV)) + ? HtmlTree.NAV() + : new HtmlTree(HtmlTag.DIV); + htmlTree.addStyle(HtmlStyle.indexNav); + HtmlTree ul = new HtmlTree(HtmlTag.UL); + addAllClassesLink(ul); + htmlTree.addContent(ul); + body.addContent(htmlTree); + addPackagesList(packages, text, tableSummary, body); + } + } + + /** + * Adds the doctitle to the documentation tree, if it is specified on the command line. + * + * @param body the document tree to which the title will be added + */ + protected void addConfigurationTitle(Content body) { + if (configuration.doctitle.length() > 0) { + Content title = new RawHtml(configuration.doctitle); + Content heading = HtmlTree.HEADING(HtmlConstants.TITLE_HEADING, + HtmlStyle.title, title); + Content div = HtmlTree.DIV(HtmlStyle.header, heading); + body.addContent(div); + } + } + + /** + * Returns highlighted "Overview", in the navigation bar as this is the + * overview page. + * + * @return a Content object to be added to the documentation tree + */ + protected Content getNavLinkContents() { + Content li = HtmlTree.LI(HtmlStyle.navBarCell1Rev, overviewLabel); + return li; + } + + /** + * Do nothing. This will be overridden. + * + * @param div the document tree to which the all classes link will be added + */ + protected void addAllClassesLink(Content div) { + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AbstractTreeWriter.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AbstractTreeWriter.java new file mode 100644 index 00000000000..68ede24c0cf --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AbstractTreeWriter.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; +import java.util.*; + +import javax.lang.model.element.TypeElement; + +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.ClassTree; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; + + +/** + * Abstract class to print the class hierarchy page for all the Classes. This + * is sub-classed by {@link PackageTreeWriter} and {@link TreeWriter} to + * generate the Package Tree and global Tree(for all the classes and packages) + * pages. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Atul M Dambalkar + */ +public abstract class AbstractTreeWriter extends HtmlDocletWriter { + + /** + * The class and interface tree built by using {@link ClassTree} + */ + protected final ClassTree classtree; + + /** + * Constructor initializes classtree variable. This constructor will be used + * while generating global tree file "overview-tree.html". + * + * @param configuration The current configuration + * @param filename File to be generated. + * @param classtree Tree built by {@link ClassTree}. + * @throws IOException + * @throws DocletAbortException + */ + protected AbstractTreeWriter(ConfigurationImpl configuration, + DocPath filename, ClassTree classtree) + throws IOException { + super(configuration, filename); + this.classtree = classtree; + } + + /** + * Add each level of the class tree. For each sub-class or + * sub-interface indents the next level information. + * Recurses itself to add sub-classes info. + * + * @param parent the superclass or superinterface of the sset + * @param collection a collection of the sub-classes at this level + * @param isEnum true if we are generating a tree for enums + * @param contentTree the content tree to which the level information will be added + */ + protected void addLevelInfo(TypeElement parent, Collection collection, + boolean isEnum, Content contentTree) { + if (!collection.isEmpty()) { + Content ul = new HtmlTree(HtmlTag.UL); + for (TypeElement local : collection) { + HtmlTree li = new HtmlTree(HtmlTag.LI); + li.addStyle(HtmlStyle.circle); + addPartialInfo(local, li); + addExtendsImplements(parent, local, li); + addLevelInfo(local, classtree.directSubClasses(local, isEnum), + isEnum, li); // Recurse + ul.addContent(li); + } + contentTree.addContent(ul); + } + } + + /** + * Add the heading for the tree depending upon tree type if it's a + * Class Tree or Interface tree. + * + * @param sset classes which are at the most base level, all the + * other classes in this run will derive from these classes + * @param heading heading for the tree + * @param div the content tree to which the tree will be added + */ + protected void addTree(SortedSet sset, String heading, HtmlTree div) { + addTree(sset, heading, div, false); + } + + protected void addTree(SortedSet sset, String heading, + HtmlTree div, boolean isEnums) { + if (!sset.isEmpty()) { + TypeElement firstTypeElement = sset.first(); + Content headingContent = getResource(heading); + Content sectionHeading = HtmlTree.HEADING(HtmlConstants.CONTENT_HEADING, true, + headingContent); + HtmlTree htmlTree; + if (configuration.allowTag(HtmlTag.SECTION)) { + htmlTree = HtmlTree.SECTION(sectionHeading); + } else { + div.addContent(sectionHeading); + htmlTree = div; + } + addLevelInfo(!utils.isInterface(firstTypeElement) ? firstTypeElement : null, + sset, isEnums, htmlTree); + if (configuration.allowTag(HtmlTag.SECTION)) { + div.addContent(htmlTree); + } + } + } + + /** + * Add information regarding the classes which this class extends or + * implements. + * + * @param parent the parent class of the class being documented + * @param typeElement the TypeElement under consideration + * @param contentTree the content tree to which the information will be added + */ + protected void addExtendsImplements(TypeElement parent, TypeElement typeElement, + Content contentTree) { + SortedSet interfaces = new TreeSet<>(utils.makeGeneralPurposeComparator()); + typeElement.getInterfaces().stream().forEach((t) -> { + interfaces.add(utils.asTypeElement(t)); + }); + if (interfaces.size() > (utils.isInterface(typeElement) ? 1 : 0)) { + boolean isFirst = true; + for (TypeElement intf : interfaces) { + if (parent != intf) { + if (utils.isPublic(intf) || utils.isLinkable(intf)) { + if (isFirst) { + isFirst = false; + if (utils.isInterface(typeElement)) { + contentTree.addContent(" ("); + contentTree.addContent(getResource("doclet.also")); + contentTree.addContent(" extends "); + } else { + contentTree.addContent(" (implements "); + } + } else { + contentTree.addContent(", "); + } + addPreQualifiedClassLink(LinkInfoImpl.Kind.TREE, intf, contentTree); + } + } + } + if (!isFirst) { + contentTree.addContent(")"); + } + } + } + + /** + * Add information about the class kind, if it's a "class" or "interface". + * + * @param typeElement the class being documented + * @param contentTree the content tree to which the information will be added + */ + protected void addPartialInfo(TypeElement typeElement, Content contentTree) { + addPreQualifiedStrongClassLink(LinkInfoImpl.Kind.TREE, typeElement, contentTree); + } + + /** + * Get the tree label for the navigation bar. + * + * @return a content tree for the tree label + */ + protected Content getNavLinkTree() { + Content li = HtmlTree.LI(HtmlStyle.navBarCell1Rev, treeLabel); + return li; + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AllClassesFrameWriter.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AllClassesFrameWriter.java new file mode 100644 index 00000000000..6ba6f0afc4c --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AllClassesFrameWriter.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; + +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; + +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; +import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; +import jdk.javadoc.internal.doclets.toolkit.util.IndexBuilder; + + +/** + * Generate the file with list of all the classes in this run. This page will be + * used in the left-hand bottom frame, when "All Classes" link is clicked in + * the left-hand top frame. The name of the generated file is + * "allclasses-frame.html". + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Atul M Dambalkar + * @author Doug Kramer + * @author Bhavesh Patel (Modified) + */ +public class AllClassesFrameWriter extends HtmlDocletWriter { + + /** + * Index of all the classes. + */ + protected IndexBuilder indexbuilder; + + /** + * BR tag to be used within a document tree. + */ + final HtmlTree BR = new HtmlTree(HtmlTag.BR); + + /** + * Construct AllClassesFrameWriter object. Also initializes the indexbuilder + * variable in this class. + * @param configuration The current configuration + * @param filename Path to the file which is getting generated. + * @param indexbuilder Unicode based Index from {@link IndexBuilder} + * @throws IOException + * @throws DocletAbortException + */ + public AllClassesFrameWriter(ConfigurationImpl configuration, + DocPath filename, IndexBuilder indexbuilder) + throws IOException { + super(configuration, filename); + this.indexbuilder = indexbuilder; + } + + /** + * Create AllClassesFrameWriter object. Then use it to generate the + * "allclasses-frame.html" file. Generate the file in the current or the + * destination directory. + * + * @param indexbuilder IndexBuilder object for all classes index. + * @throws DocletAbortException + */ + public static void generate(ConfigurationImpl configuration, + IndexBuilder indexbuilder) { + AllClassesFrameWriter allclassgen; + DocPath filename = DocPaths.ALLCLASSES_FRAME; + try { + allclassgen = new AllClassesFrameWriter(configuration, + filename, indexbuilder); + allclassgen.buildAllClassesFile(true); + allclassgen.close(); + filename = DocPaths.ALLCLASSES_NOFRAME; + allclassgen = new AllClassesFrameWriter(configuration, + filename, indexbuilder); + allclassgen.buildAllClassesFile(false); + allclassgen.close(); + } catch (IOException exc) { + configuration.standardmessage. + error("doclet.exception_encountered", + exc.toString(), filename); + throw new DocletAbortException(exc); + } + } + + /** + * Print all the classes in the file. + * @param wantFrames True if we want frames. + */ + protected void buildAllClassesFile(boolean wantFrames) throws IOException { + String label = configuration.getText("doclet.All_Classes"); + Content body = getBody(false, getWindowTitle(label)); + Content heading = HtmlTree.HEADING(HtmlConstants.TITLE_HEADING, + HtmlStyle.bar, allclassesLabel); + body.addContent(heading); + Content ul = new HtmlTree(HtmlTag.UL); + // Generate the class links and add it to the tdFont tree. + addAllClasses(ul, wantFrames); + HtmlTree htmlTree = (configuration.allowTag(HtmlTag.MAIN)) + ? HtmlTree.MAIN(HtmlStyle.indexContainer, ul) + : HtmlTree.DIV(HtmlStyle.indexContainer, ul); + body.addContent(htmlTree); + printHtmlDocument(null, false, body); + } + + /** + * Use the sorted index of all the classes and add all the classes to the + * content list. + * + * @param content HtmlTree content to which all classes information will be added + * @param wantFrames True if we want frames. + */ + protected void addAllClasses(Content content, boolean wantFrames) { + for (Character unicode : indexbuilder.index()) { + addContents(indexbuilder.getMemberList(unicode), wantFrames, content); + } + } + + /** + * Given a list of classes, generate links for each class or interface. + * If the class kind is interface, print it in the italics font. Also all + * links should target the right-hand frame. If clicked on any class name + * in this page, appropriate class page should get opened in the right-hand + * frame. + * + * @param classlist Sorted list of classes. + * @param wantFrames True if we want frames. + * @param content HtmlTree content to which the links will be added + */ + protected void addContents(Iterable classlist, boolean wantFrames, + Content content) { + for (Element element : classlist) { + TypeElement typeElement = (TypeElement)element; + if (!utils.isCoreClass(typeElement)) { + continue; + } + Content label = interfaceName(typeElement, false); + Content linkContent; + if (wantFrames) { + linkContent = getLink(new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.ALL_CLASSES_FRAME, typeElement).label(label).target("classFrame")); + } else { + linkContent = getLink(new LinkInfoImpl(configuration, LinkInfoImpl.Kind.DEFAULT, typeElement).label(label)); + } + Content li = HtmlTree.LI(linkContent); + content.addContent(li); + } + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AnnotationTypeFieldWriterImpl.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AnnotationTypeFieldWriterImpl.java new file mode 100644 index 00000000000..0bb63a68f93 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AnnotationTypeFieldWriterImpl.java @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; + +import java.util.Arrays; +import java.util.List; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.AnnotationTypeFieldWriter; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.MemberSummaryWriter; + + +/** + * Writes annotation type field documentation in HTML format. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Bhavesh Patel + */ +public class AnnotationTypeFieldWriterImpl extends AbstractMemberWriter + implements AnnotationTypeFieldWriter, MemberSummaryWriter { + + /** + * Construct a new AnnotationTypeFieldWriterImpl. + * + * @param writer the writer that will write the output. + * @param annotationType the AnnotationType that holds this member. + */ + public AnnotationTypeFieldWriterImpl(SubWriterHolderWriter writer, + TypeElement annotationType) { + super(writer, annotationType); + } + + /** + * {@inheritDoc} + */ + public Content getMemberSummaryHeader(TypeElement typeElement, + Content memberSummaryTree) { + memberSummaryTree.addContent( + HtmlConstants.START_OF_ANNOTATION_TYPE_FIELD_SUMMARY); + Content memberTree = writer.getMemberTreeHeader(); + writer.addSummaryHeader(this, typeElement, memberTree); + return memberTree; + } + + /** + * {@inheritDoc} + */ + public Content getMemberTreeHeader() { + return writer.getMemberTreeHeader(); + } + + /** + * {@inheritDoc} + */ + public void addMemberTree(Content memberSummaryTree, Content memberTree) { + writer.addMemberTree(memberSummaryTree, memberTree); + } + + /** + * {@inheritDoc} + */ + public void addAnnotationFieldDetailsMarker(Content memberDetails) { + memberDetails.addContent(HtmlConstants.START_OF_ANNOTATION_TYPE_FIELD_DETAILS); + } + + /** + * {@inheritDoc} + */ + public void addAnnotationDetailsTreeHeader(TypeElement typeElement, + Content memberDetailsTree) { + if (!writer.printedAnnotationFieldHeading) { + memberDetailsTree.addContent(writer.getMarkerAnchor( + SectionName.ANNOTATION_TYPE_FIELD_DETAIL)); + Content heading = HtmlTree.HEADING(HtmlConstants.DETAILS_HEADING, + writer.fieldDetailsLabel); + memberDetailsTree.addContent(heading); + writer.printedAnnotationFieldHeading = true; + } + } + + /** + * {@inheritDoc} + */ + public Content getAnnotationDocTreeHeader(Element member, + Content annotationDetailsTree) { + annotationDetailsTree.addContent( + writer.getMarkerAnchor(name(member))); + Content annotationDocTree = writer.getMemberTreeHeader(); + Content heading = new HtmlTree(HtmlConstants.MEMBER_HEADING); + heading.addContent(name(member)); + annotationDocTree.addContent(heading); + return annotationDocTree; + } + + /** + * {@inheritDoc} + */ + public Content getSignature(Element member) { + Content pre = new HtmlTree(HtmlTag.PRE); + writer.addAnnotationInfo(member, pre); + addModifiers(member, pre); + Content link = + writer.getLink(new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.MEMBER, getType(member))); + pre.addContent(link); + pre.addContent(writer.getSpace()); + if (configuration.linksource) { + Content memberName = new StringContent(name(member)); + writer.addSrcLink(member, memberName, pre); + } else { + addName(name(member), pre); + } + return pre; + } + + /** + * {@inheritDoc} + */ + public void addDeprecated(Element member, Content annotationDocTree) { + addDeprecatedInfo(member, annotationDocTree); + } + + /** + * {@inheritDoc} + */ + public void addComments(Element member, Content annotationDocTree) { + addComment(member, annotationDocTree); + } + + /** + * {@inheritDoc} + */ + public void addTags(Element member, Content annotationDocTree) { + writer.addTagsInfo(member, annotationDocTree); + } + + /** + * {@inheritDoc} + */ + public Content getAnnotationDetails(Content annotationDetailsTree) { + if (configuration.allowTag(HtmlTag.SECTION)) { + HtmlTree htmlTree = HtmlTree.SECTION(getMemberTree(annotationDetailsTree)); + return htmlTree; + } + return getMemberTree(annotationDetailsTree); + } + + /** + * {@inheritDoc} + */ + public Content getAnnotationDoc(Content annotationDocTree, + boolean isLastContent) { + return getMemberTree(annotationDocTree, isLastContent); + } + + /** + * Close the writer. + */ + public void close() throws IOException { + writer.close(); + } + + /** + * {@inheritDoc} + */ + public void addSummaryLabel(Content memberTree) { + Content label = HtmlTree.HEADING(HtmlConstants.SUMMARY_HEADING, + writer.getResource("doclet.Field_Summary")); + memberTree.addContent(label); + } + + /** + * {@inheritDoc} + */ + public String getTableSummary() { + return configuration.getText("doclet.Member_Table_Summary", + configuration.getText("doclet.Field_Summary"), + configuration.getText("doclet.fields")); + } + + /** + * {@inheritDoc} + */ + public Content getCaption() { + return configuration.getResource("doclet.Fields"); + } + + /** + * {@inheritDoc} + */ + public List getSummaryTableHeader(Element member) { + List header = Arrays.asList(writer.getModifierTypeHeader(), + configuration.getText("doclet.0_and_1", + configuration.getText("doclet.Fields"), + configuration.getText("doclet.Description"))); + return header; + } + + /** + * {@inheritDoc} + */ + public void addSummaryAnchor(TypeElement typeElement, Content memberTree) { + memberTree.addContent(writer.getMarkerAnchor( + SectionName.ANNOTATION_TYPE_FIELD_SUMMARY)); + } + + /** + * {@inheritDoc} + */ + public void addInheritedSummaryAnchor(TypeElement typeElement, Content inheritedTree) { + } + + /** + * {@inheritDoc} + */ + public void addInheritedSummaryLabel(TypeElement typeElement, Content inheritedTree) { + } + + /** + * {@inheritDoc} + */ + protected void addSummaryLink(LinkInfoImpl.Kind context, TypeElement typeElement, Element member, + Content tdSummary) { + Content memberLink = HtmlTree.SPAN(HtmlStyle.memberNameLink, + writer.getDocLink(context, member, name(member), false)); + Content code = HtmlTree.CODE(memberLink); + tdSummary.addContent(code); + } + + /** + * {@inheritDoc} + */ + protected void addInheritedSummaryLink(TypeElement typeElement, + Element member, Content linksTree) { + //Not applicable. + } + + /** + * {@inheritDoc} + */ + protected void addSummaryType(Element member, Content tdSummaryType) { + addModifierAndType(member, getType(member), tdSummaryType); + } + + /** + * {@inheritDoc} + */ + protected Content getDeprecatedLink(Element member) { + return writer.getDocLink(LinkInfoImpl.Kind.MEMBER, + member, utils.getFullyQualifiedName(member)); + } + + /** + * {@inheritDoc} + */ + protected Content getNavSummaryLink(TypeElement typeElement, boolean link) { + if (link) { + return writer.getHyperLink( + SectionName.ANNOTATION_TYPE_FIELD_SUMMARY, + writer.getResource("doclet.navField")); + } else { + return writer.getResource("doclet.navField"); + } + } + + /** + * {@inheritDoc} + */ + protected void addNavDetailLink(boolean link, Content liNav) { + if (link) { + liNav.addContent(writer.getHyperLink( + SectionName.ANNOTATION_TYPE_FIELD_DETAIL, + writer.getResource("doclet.navField"))); + } else { + liNav.addContent(writer.getResource("doclet.navField")); + } + } + private TypeMirror getType(Element member) { + if (utils.isConstructor(member)) + return null; + if (utils.isExecutableElement(member)) + return utils.getReturnType((ExecutableElement)member); + return member.asType(); + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AnnotationTypeOptionalMemberWriterImpl.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AnnotationTypeOptionalMemberWriterImpl.java new file mode 100644 index 00000000000..221e467101b --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AnnotationTypeOptionalMemberWriterImpl.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; + +import java.util.Arrays; +import java.util.List; + +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; + +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.AnnotationTypeOptionalMemberWriter; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.MemberSummaryWriter; + + +/** + * Writes annotation type optional member documentation in HTML format. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Jamie Ho + * @author Bhavesh Patel (Modified) + */ +public class AnnotationTypeOptionalMemberWriterImpl extends + AnnotationTypeRequiredMemberWriterImpl + implements AnnotationTypeOptionalMemberWriter, MemberSummaryWriter { + + /** + * Construct a new AnnotationTypeOptionalMemberWriterImpl. + * + * @param writer the writer that will write the output. + * @param annotationType the AnnotationType that holds this member. + */ + public AnnotationTypeOptionalMemberWriterImpl(SubWriterHolderWriter writer, + TypeElement annotationType) { + super(writer, annotationType); + } + + /** + * {@inheritDoc} + */ + public Content getMemberSummaryHeader(TypeElement typeElement, + Content memberSummaryTree) { + memberSummaryTree.addContent( + HtmlConstants.START_OF_ANNOTATION_TYPE_OPTIONAL_MEMBER_SUMMARY); + Content memberTree = writer.getMemberTreeHeader(); + writer.addSummaryHeader(this, typeElement, memberTree); + return memberTree; + } + + /** + * {@inheritDoc} + */ + public void addMemberTree(Content memberSummaryTree, Content memberTree) { + writer.addMemberTree(memberSummaryTree, memberTree); + } + + /** + * {@inheritDoc} + */ + public void addDefaultValueInfo(Element member, Content annotationDocTree) { + if (utils.isAnnotationType(member)) { + ExecutableElement ee = (ExecutableElement)member; + AnnotationValue value = ee.getDefaultValue(); + if (value != null) { + Content dt = HtmlTree.DT(writer.getResource("doclet.Default")); + Content dl = HtmlTree.DL(dt); + Content dd = HtmlTree.DD(new StringContent(value.toString())); + dl.addContent(dd); + annotationDocTree.addContent(dl); + } + } + } + + /** + * {@inheritDoc} + */ + public void close() throws IOException { + writer.close(); + } + + /** + * {@inheritDoc} + */ + public void addSummaryLabel(Content memberTree) { + Content label = HtmlTree.HEADING(HtmlConstants.SUMMARY_HEADING, + writer.getResource("doclet.Annotation_Type_Optional_Member_Summary")); + memberTree.addContent(label); + } + + /** + * {@inheritDoc} + */ + public String getTableSummary() { + return configuration.getText("doclet.Member_Table_Summary", + configuration.getText("doclet.Annotation_Type_Optional_Member_Summary"), + configuration.getText("doclet.annotation_type_optional_members")); + } + + /** + * {@inheritDoc} + */ + public Content getCaption() { + return configuration.getResource("doclet.Annotation_Type_Optional_Members"); + } + + /** + * {@inheritDoc} + */ + public List getSummaryTableHeader(Element member) { + List header = Arrays.asList(writer.getModifierTypeHeader(), + configuration.getText("doclet.0_and_1", + configuration.getText("doclet.Annotation_Type_Optional_Member"), + configuration.getText("doclet.Description"))); + return header; + } + + /** + * {@inheritDoc} + */ + public void addSummaryAnchor(TypeElement typeElement, Content memberTree) { + memberTree.addContent(writer.getMarkerAnchor( + SectionName.ANNOTATION_TYPE_OPTIONAL_ELEMENT_SUMMARY)); + } + + /** + * {@inheritDoc} + */ + protected Content getNavSummaryLink(TypeElement typeElement, boolean link) { + if (link) { + return writer.getHyperLink( + SectionName.ANNOTATION_TYPE_OPTIONAL_ELEMENT_SUMMARY, + writer.getResource("doclet.navAnnotationTypeOptionalMember")); + } else { + return writer.getResource("doclet.navAnnotationTypeOptionalMember"); + } + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AnnotationTypeRequiredMemberWriterImpl.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AnnotationTypeRequiredMemberWriterImpl.java new file mode 100644 index 00000000000..531d0a033f5 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AnnotationTypeRequiredMemberWriterImpl.java @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; +import java.util.Arrays; +import java.util.List; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.AnnotationTypeRequiredMemberWriter; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.MemberSummaryWriter; + + +/** + * Writes annotation type required member documentation in HTML format. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Jamie Ho + * @author Bhavesh Patel (Modified) + */ +public class AnnotationTypeRequiredMemberWriterImpl extends AbstractMemberWriter + implements AnnotationTypeRequiredMemberWriter, MemberSummaryWriter { + + /** + * Construct a new AnnotationTypeRequiredMemberWriterImpl. + * + * @param writer the writer that will write the output. + * @param annotationType the AnnotationType that holds this member. + */ + public AnnotationTypeRequiredMemberWriterImpl(SubWriterHolderWriter writer, + TypeElement annotationType) { + super(writer, annotationType); + } + + /** + * {@inheritDoc} + */ + public Content getMemberSummaryHeader(TypeElement typeElement, + Content memberSummaryTree) { + memberSummaryTree.addContent( + HtmlConstants.START_OF_ANNOTATION_TYPE_REQUIRED_MEMBER_SUMMARY); + Content memberTree = writer.getMemberTreeHeader(); + writer.addSummaryHeader(this, typeElement, memberTree); + return memberTree; + } + + /** + * {@inheritDoc} + */ + public Content getMemberTreeHeader() { + return writer.getMemberTreeHeader(); + } + + /** + * {@inheritDoc} + */ + public void addMemberTree(Content memberSummaryTree, Content memberTree) { + writer.addMemberTree(memberSummaryTree, memberTree); + } + + /** + * {@inheritDoc} + */ + public void addAnnotationDetailsMarker(Content memberDetails) { + memberDetails.addContent(HtmlConstants.START_OF_ANNOTATION_TYPE_DETAILS); + } + + /** + * {@inheritDoc} + */ + public void addAnnotationDetailsTreeHeader(TypeElement classDoc, + Content memberDetailsTree) { + if (!writer.printedAnnotationHeading) { + memberDetailsTree.addContent(writer.getMarkerAnchor( + SectionName.ANNOTATION_TYPE_ELEMENT_DETAIL)); + Content heading = HtmlTree.HEADING(HtmlConstants.DETAILS_HEADING, + writer.annotationTypeDetailsLabel); + memberDetailsTree.addContent(heading); + writer.printedAnnotationHeading = true; + } + } + + /** + * {@inheritDoc} + */ + public Content getAnnotationDocTreeHeader(Element member, + Content annotationDetailsTree) { + String simpleName = name(member); + annotationDetailsTree.addContent(writer.getMarkerAnchor(simpleName + + utils.signature((ExecutableElement) member))); + Content annotationDocTree = writer.getMemberTreeHeader(); + Content heading = new HtmlTree(HtmlConstants.MEMBER_HEADING); + heading.addContent(simpleName); + annotationDocTree.addContent(heading); + return annotationDocTree; + } + + /** + * {@inheritDoc} + */ + public Content getSignature(Element member) { + Content pre = new HtmlTree(HtmlTag.PRE); + writer.addAnnotationInfo(member, pre); + addModifiers(member, pre); + Content link = + writer.getLink(new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.MEMBER, getType(member))); + pre.addContent(link); + pre.addContent(writer.getSpace()); + if (configuration.linksource) { + Content memberName = new StringContent(name(member)); + writer.addSrcLink(member, memberName, pre); + } else { + addName(name(member), pre); + } + return pre; + } + + /** + * {@inheritDoc} + */ + public void addDeprecated(Element member, Content annotationDocTree) { + addDeprecatedInfo(member, annotationDocTree); + } + + /** + * {@inheritDoc} + */ + public void addComments(Element member, Content annotationDocTree) { + addComment(member, annotationDocTree); + } + + /** + * {@inheritDoc} + */ + public void addTags(Element member, Content annotationDocTree) { + writer.addTagsInfo(member, annotationDocTree); + } + + /** + * {@inheritDoc} + */ + public Content getAnnotationDetails(Content annotationDetailsTree) { + if (configuration.allowTag(HtmlTag.SECTION)) { + HtmlTree htmlTree = HtmlTree.SECTION(getMemberTree(annotationDetailsTree)); + return htmlTree; + } + return getMemberTree(annotationDetailsTree); + } + + /** + * {@inheritDoc} + */ + public Content getAnnotationDoc(Content annotationDocTree, + boolean isLastContent) { + return getMemberTree(annotationDocTree, isLastContent); + } + + /** + * Close the writer. + */ + public void close() throws IOException { + writer.close(); + } + + /** + * {@inheritDoc} + */ + public void addSummaryLabel(Content memberTree) { + Content label = HtmlTree.HEADING(HtmlConstants.SUMMARY_HEADING, + writer.getResource("doclet.Annotation_Type_Required_Member_Summary")); + memberTree.addContent(label); + } + + /** + * {@inheritDoc} + */ + public String getTableSummary() { + return configuration.getText("doclet.Member_Table_Summary", + configuration.getText("doclet.Annotation_Type_Required_Member_Summary"), + configuration.getText("doclet.annotation_type_required_members")); + } + + /** + * {@inheritDoc} + */ + public Content getCaption() { + return configuration.getResource("doclet.Annotation_Type_Required_Members"); + } + + /** + * {@inheritDoc} + */ + public List getSummaryTableHeader(Element member) { + List header = Arrays.asList(writer.getModifierTypeHeader(), + configuration.getText("doclet.0_and_1", + configuration.getText("doclet.Annotation_Type_Required_Member"), + configuration.getText("doclet.Description"))); + return header; + } + + /** + * {@inheritDoc} + */ + public void addSummaryAnchor(TypeElement typeElement, Content memberTree) { + memberTree.addContent(writer.getMarkerAnchor( + SectionName.ANNOTATION_TYPE_REQUIRED_ELEMENT_SUMMARY)); + } + + /** + * {@inheritDoc} + */ + public void addInheritedSummaryAnchor(TypeElement typeElement, Content inheritedTree) { + } + + /** + * {@inheritDoc} + */ + public void addInheritedSummaryLabel(TypeElement typeElement, Content inheritedTree) { + } + + /** + * {@inheritDoc} + */ + protected void addSummaryLink(LinkInfoImpl.Kind context, TypeElement typeElement, Element member, + Content tdSummary) { + Content memberLink = HtmlTree.SPAN(HtmlStyle.memberNameLink, + writer.getDocLink(context, member, name(member), false)); + Content code = HtmlTree.CODE(memberLink); + tdSummary.addContent(code); + } + + /** + * {@inheritDoc} + */ + protected void addInheritedSummaryLink(TypeElement typeElement, + Element member, Content linksTree) { + //Not applicable. + } + + /** + * {@inheritDoc} + */ + protected void addSummaryType(Element member, Content tdSummaryType) { + addModifierAndType(member, getType(member), tdSummaryType); + } + + /** + * {@inheritDoc} + */ + protected Content getDeprecatedLink(Element member) { + String name = utils.getFullyQualifiedName(member) + "." + member.getSimpleName(); + return writer.getDocLink(LinkInfoImpl.Kind.MEMBER, member, name); + } + + /** + * {@inheritDoc} + */ + protected Content getNavSummaryLink(TypeElement typeElement, boolean link) { + if (link) { + return writer.getHyperLink( + SectionName.ANNOTATION_TYPE_REQUIRED_ELEMENT_SUMMARY, + writer.getResource("doclet.navAnnotationTypeRequiredMember")); + } else { + return writer.getResource("doclet.navAnnotationTypeRequiredMember"); + } + } + + /** + * {@inheritDoc} + */ + protected void addNavDetailLink(boolean link, Content liNav) { + if (link) { + liNav.addContent(writer.getHyperLink( + SectionName.ANNOTATION_TYPE_ELEMENT_DETAIL, + writer.getResource("doclet.navAnnotationTypeMember"))); + } else { + liNav.addContent(writer.getResource("doclet.navAnnotationTypeMember")); + } + } + + private TypeMirror getType(Element member) { + return utils.isExecutableElement(member) + ? utils.getReturnType((ExecutableElement) member) + : member.asType(); + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AnnotationTypeWriterImpl.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AnnotationTypeWriterImpl.java new file mode 100644 index 00000000000..3d6a248da57 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AnnotationTypeWriterImpl.java @@ -0,0 +1,465 @@ +/* + * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.IOException; +import java.util.List; + +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + +import com.sun.source.doctree.DocTree; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.AnnotationTypeWriter; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.builders.MemberSummaryBuilder; +import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; +import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; +import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberMap; + +/** + * Generate the Class Information Page. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @see java.util.Collections + * @see java.util.List + * @see java.util.ArrayList + * @see java.util.HashMap + * + * @author Atul M Dambalkar + * @author Robert Field + * @author Bhavesh Patel (Modified) + */ +public class AnnotationTypeWriterImpl extends SubWriterHolderWriter + implements AnnotationTypeWriter { + + protected TypeElement annotationType; + + protected TypeMirror prev; + + protected TypeMirror next; + + /** + * @param configuration the configuration + * @param annotationType the annotation type being documented. + * @param prevType the previous class that was documented. + * @param nextType the next class being documented. + * @throws java.lang.Exception + */ + public AnnotationTypeWriterImpl(ConfigurationImpl configuration, + TypeElement annotationType, TypeMirror prevType, TypeMirror nextType) + throws Exception { + super(configuration, DocPath.forClass(configuration.utils, annotationType)); + this.annotationType = annotationType; + configuration.currentTypeElement = annotationType; + this.prev = prevType; + this.next = nextType; + } + + /** + * Get this package link. + * + * @return a content tree for the package link + */ + @Override + protected Content getNavLinkPackage() { + Content linkContent = getHyperLink(DocPaths.PACKAGE_SUMMARY, + packageLabel); + Content li = HtmlTree.LI(linkContent); + return li; + } + + /** + * Get the class link. + * + * @return a content tree for the class link + */ + @Override + protected Content getNavLinkClass() { + Content li = HtmlTree.LI(HtmlStyle.navBarCell1Rev, classLabel); + return li; + } + + /** + * Get the class use link. + * + * @return a content tree for the class use link + */ + @Override + protected Content getNavLinkClassUse() { + Content linkContent = getHyperLink(DocPaths.CLASS_USE.resolve(filename), useLabel); + Content li = HtmlTree.LI(linkContent); + return li; + } + + /** + * Get link to previous class. + * + * @return a content tree for the previous class link + */ + @Override + public Content getNavLinkPrevious() { + Content li; + if (prev != null) { + Content prevLink = getLink(new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.CLASS, utils.asTypeElement(prev)) + .label(prevclassLabel).strong(true)); + li = HtmlTree.LI(prevLink); + } + else + li = HtmlTree.LI(prevclassLabel); + return li; + } + + /** + * Get link to next class. + * + * @return a content tree for the next class link + */ + @Override + public Content getNavLinkNext() { + Content li; + if (next != null) { + Content nextLink = getLink(new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.CLASS, utils.asTypeElement(next)) + .label(nextclassLabel).strong(true)); + li = HtmlTree.LI(nextLink); + } + else + li = HtmlTree.LI(nextclassLabel); + return li; + } + + /** + * {@inheritDoc} + */ + @Override + public Content getHeader(String header) { + HtmlTree bodyTree = getBody(true, getWindowTitle(utils.getSimpleName(annotationType))); + HtmlTree htmlTree = (configuration.allowTag(HtmlTag.HEADER)) + ? HtmlTree.HEADER() + : bodyTree; + addTop(htmlTree); + addNavLinks(true, htmlTree); + if (configuration.allowTag(HtmlTag.HEADER)) { + bodyTree.addContent(htmlTree); + } + bodyTree.addContent(HtmlConstants.START_OF_CLASS_DATA); + HtmlTree div = new HtmlTree(HtmlTag.DIV); + div.addStyle(HtmlStyle.header); + PackageElement pkg = utils.containingPackage(annotationType); + if (!pkg.isUnnamed()) { + Content pkgNameContent = new StringContent(utils.getPackageName(pkg)); + Content pkgNameDiv = HtmlTree.DIV(HtmlStyle.subTitle, pkgNameContent); + div.addContent(pkgNameDiv); + } + LinkInfoImpl linkInfo = new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.CLASS_HEADER, annotationType); + Content headerContent = new StringContent(header); + Content heading = HtmlTree.HEADING(HtmlConstants.CLASS_PAGE_HEADING, true, + HtmlStyle.title, headerContent); + heading.addContent(getTypeParameterLinks(linkInfo)); + div.addContent(heading); + if (configuration.allowTag(HtmlTag.MAIN)) { + mainTree.addContent(div); + } else { + bodyTree.addContent(div); + } + return bodyTree; + } + + /** + * {@inheritDoc} + */ + @Override + public Content getAnnotationContentHeader() { + return getContentHeader(); + } + + /** + * {@inheritDoc} + */ + @Override + public void addFooter(Content contentTree) { + contentTree.addContent(HtmlConstants.END_OF_CLASS_DATA); + Content htmlTree = (configuration.allowTag(HtmlTag.FOOTER)) + ? HtmlTree.FOOTER() + : contentTree; + addNavLinks(false, htmlTree); + addBottom(htmlTree); + if (configuration.allowTag(HtmlTag.FOOTER)) { + contentTree.addContent(htmlTree); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void printDocument(Content contentTree) throws IOException { + printHtmlDocument(configuration.metakeywords.getMetaKeywords(annotationType), + true, contentTree); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getAnnotationInfoTreeHeader() { + return getMemberTreeHeader(); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getAnnotationInfo(Content annotationInfoTree) { + return getMemberTree(HtmlStyle.description, annotationInfoTree); + } + + /** + * {@inheritDoc} + */ + @Override + public void addAnnotationTypeSignature(String modifiers, Content annotationInfoTree) { + annotationInfoTree.addContent(new HtmlTree(HtmlTag.BR)); + Content pre = new HtmlTree(HtmlTag.PRE); + addAnnotationInfo(annotationType, pre); + pre.addContent(modifiers); + LinkInfoImpl linkInfo = new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.CLASS_SIGNATURE, annotationType); + Content annotationName = new StringContent(utils.getSimpleName(annotationType)); + Content parameterLinks = getTypeParameterLinks(linkInfo); + if (configuration.linksource) { + addSrcLink(annotationType, annotationName, pre); + pre.addContent(parameterLinks); + } else { + Content span = HtmlTree.SPAN(HtmlStyle.memberNameLabel, annotationName); + span.addContent(parameterLinks); + pre.addContent(span); + } + annotationInfoTree.addContent(pre); + } + + /** + * {@inheritDoc} + */ + @Override + public void addAnnotationTypeDescription(Content annotationInfoTree) { + if(!configuration.nocomment) { + if (!utils.getBody(annotationType).isEmpty()) { + addInlineComment(annotationType, annotationInfoTree); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void addAnnotationTypeTagInfo(Content annotationInfoTree) { + if(!configuration.nocomment) { + addTagsInfo(annotationType, annotationInfoTree); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void addAnnotationTypeDeprecationInfo(Content annotationInfoTree) { + Content hr = new HtmlTree(HtmlTag.HR); + annotationInfoTree.addContent(hr); + List deprs = utils.getBlockTags(annotationType, DocTree.Kind.DEPRECATED); + if (utils.isDeprecated(annotationType)) { + CommentHelper ch = utils.getCommentHelper(annotationType); + Content deprLabel = HtmlTree.SPAN(HtmlStyle.deprecatedLabel, deprecatedPhrase); + Content div = HtmlTree.DIV(HtmlStyle.block, deprLabel); + if (!deprs.isEmpty()) { + + List commentTags = ch.getDescription(configuration, deprs.get(0)); + if (!commentTags.isEmpty()) { + div.addContent(getSpace()); + addInlineDeprecatedComment(annotationType, deprs.get(0), div); + } + } + annotationInfoTree.addContent(div); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected Content getNavLinkTree() { + Content treeLinkContent = getHyperLink(DocPaths.PACKAGE_TREE, + treeLabel, "", ""); + Content li = HtmlTree.LI(treeLinkContent); + return li; + } + + /** + * Add summary details to the navigation bar. + * + * @param subDiv the content tree to which the summary detail links will be added + */ + @Override + protected void addSummaryDetailLinks(Content subDiv) { + try { + Content div = HtmlTree.DIV(getNavSummaryLinks()); + div.addContent(getNavDetailLinks()); + subDiv.addContent(div); + } catch (Exception e) { + throw new DocletAbortException(e); + } + } + + /** + * Get summary links for navigation bar. + * + * @return the content tree for the navigation summary links + * @throws java.lang.Exception + */ + protected Content getNavSummaryLinks() throws Exception { + Content li = HtmlTree.LI(summaryLabel); + li.addContent(getSpace()); + Content ulNav = HtmlTree.UL(HtmlStyle.subNavList, li); + MemberSummaryBuilder memberSummaryBuilder = (MemberSummaryBuilder) + configuration.getBuilderFactory().getMemberSummaryBuilder(this); + Content liNavField = new HtmlTree(HtmlTag.LI); + addNavSummaryLink(memberSummaryBuilder, + "doclet.navField", + VisibleMemberMap.Kind.ANNOTATION_TYPE_FIELDS, liNavField); + addNavGap(liNavField); + ulNav.addContent(liNavField); + Content liNavReq = new HtmlTree(HtmlTag.LI); + addNavSummaryLink(memberSummaryBuilder, + "doclet.navAnnotationTypeRequiredMember", + VisibleMemberMap.Kind.ANNOTATION_TYPE_MEMBER_REQUIRED, liNavReq); + addNavGap(liNavReq); + ulNav.addContent(liNavReq); + Content liNavOpt = new HtmlTree(HtmlTag.LI); + addNavSummaryLink(memberSummaryBuilder, + "doclet.navAnnotationTypeOptionalMember", + VisibleMemberMap.Kind.ANNOTATION_TYPE_MEMBER_OPTIONAL, liNavOpt); + ulNav.addContent(liNavOpt); + return ulNav; + } + + /** + * Add the navigation summary link. + * + * @param builder builder for the member to be documented + * @param label the label for the navigation + * @param type type to be documented + * @param liNav the content tree to which the navigation summary link will be added + */ + protected void addNavSummaryLink(MemberSummaryBuilder builder, + String label, VisibleMemberMap.Kind type, Content liNav) { + AbstractMemberWriter writer = ((AbstractMemberWriter) builder. + getMemberSummaryWriter(type)); + if (writer == null) { + liNav.addContent(getResource(label)); + } else { + liNav.addContent(writer.getNavSummaryLink(null, + ! builder.getVisibleMemberMap(type).noVisibleMembers())); + } + } + + /** + * Get detail links for the navigation bar. + * + * @return the content tree for the detail links + * @throws java.lang.Exception + */ + protected Content getNavDetailLinks() throws Exception { + Content li = HtmlTree.LI(detailLabel); + li.addContent(getSpace()); + Content ulNav = HtmlTree.UL(HtmlStyle.subNavList, li); + MemberSummaryBuilder memberSummaryBuilder = (MemberSummaryBuilder) + configuration.getBuilderFactory().getMemberSummaryBuilder(this); + AbstractMemberWriter writerField = + ((AbstractMemberWriter) memberSummaryBuilder. + getMemberSummaryWriter(VisibleMemberMap.Kind.ANNOTATION_TYPE_FIELDS)); + AbstractMemberWriter writerOptional = + ((AbstractMemberWriter) memberSummaryBuilder. + getMemberSummaryWriter(VisibleMemberMap.Kind.ANNOTATION_TYPE_MEMBER_OPTIONAL)); + AbstractMemberWriter writerRequired = + ((AbstractMemberWriter) memberSummaryBuilder. + getMemberSummaryWriter(VisibleMemberMap.Kind.ANNOTATION_TYPE_MEMBER_REQUIRED)); + Content liNavField = new HtmlTree(HtmlTag.LI); + if (writerField != null) { + writerField.addNavDetailLink(!utils.getAnnotationFields(annotationType).isEmpty(), liNavField); + } else { + liNavField.addContent(getResource("doclet.navField")); + } + addNavGap(liNavField); + ulNav.addContent(liNavField); + if (writerOptional != null){ + Content liNavOpt = new HtmlTree(HtmlTag.LI); + writerOptional.addNavDetailLink(!annotationType.getAnnotationMirrors().isEmpty(), liNavOpt); + ulNav.addContent(liNavOpt); + } else if (writerRequired != null){ + Content liNavReq = new HtmlTree(HtmlTag.LI); + writerRequired.addNavDetailLink(!annotationType.getAnnotationMirrors().isEmpty(), liNavReq); + ulNav.addContent(liNavReq); + } else { + Content liNav = HtmlTree.LI(getResource("doclet.navAnnotationTypeMember")); + ulNav.addContent(liNav); + } + return ulNav; + } + + /** + * Add gap between navigation bar elements. + * + * @param liNav the content tree to which the gap will be added + */ + protected void addNavGap(Content liNav) { + liNav.addContent(getSpace()); + liNav.addContent("|"); + liNav.addContent(getSpace()); + } + + /** + * {@inheritDoc} + */ + @Override + public TypeElement getAnnotationTypeElement() { + return annotationType; + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ClassUseWriter.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ClassUseWriter.java new file mode 100644 index 00000000000..9e3834dffba --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ClassUseWriter.java @@ -0,0 +1,554 @@ +/* + * Copyright (c) 1998, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import javax.lang.model.element.Element; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; + +import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.ClassTree; +import jdk.javadoc.internal.doclets.toolkit.util.ClassUseMapper; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; +import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; + +/** + * Generate class usage information. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Robert G. Field + * @author Bhavesh Patel (Modified) + */ +public class ClassUseWriter extends SubWriterHolderWriter { + + final TypeElement typeElement; + Set pkgToPackageAnnotations = null; + final Map> pkgToClassTypeParameter; + final Map> pkgToClassAnnotations; + final Map> pkgToMethodTypeParameter; + final Map> pkgToMethodArgTypeParameter; + final Map> pkgToMethodReturnTypeParameter; + final Map> pkgToMethodAnnotations; + final Map> pkgToMethodParameterAnnotations; + final Map> pkgToFieldTypeParameter; + final Map> pkgToFieldAnnotations; + final Map> pkgToSubclass; + final Map> pkgToSubinterface; + final Map> pkgToImplementingClass; + final Map> pkgToField; + final Map> pkgToMethodReturn; + final Map> pkgToMethodArgs; + final Map> pkgToMethodThrows; + final Map> pkgToConstructorAnnotations; + final Map> pkgToConstructorParameterAnnotations; + final Map> pkgToConstructorArgs; + final Map> pkgToConstructorArgTypeParameter; + final Map> pkgToConstructorThrows; + final SortedSet pkgSet; + final MethodWriterImpl methodSubWriter; + final ConstructorWriterImpl constrSubWriter; + final FieldWriterImpl fieldSubWriter; + final NestedClassWriterImpl classSubWriter; + // Summary for various use tables. + final String classUseTableSummary; + final String subclassUseTableSummary; + final String subinterfaceUseTableSummary; + final String fieldUseTableSummary; + final String methodUseTableSummary; + final String constructorUseTableSummary; + + /** + * The HTML tree for main tag. + */ + protected HtmlTree mainTree = HtmlTree.MAIN(); + + /** + * Constructor. + * + * @param filename the file to be generated. + * @throws IOException + * @throws DocletAbortException + */ + public ClassUseWriter(ConfigurationImpl configuration, + ClassUseMapper mapper, DocPath filename, + TypeElement typeElement) throws IOException { + super(configuration, filename); + this.typeElement = typeElement; + if (mapper.classToPackageAnnotations.containsKey(typeElement)) { + pkgToPackageAnnotations = new TreeSet<>(utils.makeClassUseComparator()); + pkgToPackageAnnotations.addAll(mapper.classToPackageAnnotations.get(typeElement)); + } + configuration.currentTypeElement = typeElement; + this.pkgSet = new TreeSet<>(utils.makePackageComparator()); + this.pkgToClassTypeParameter = pkgDivide(mapper.classToClassTypeParam); + this.pkgToClassAnnotations = pkgDivide(mapper.classToClassAnnotations); + this.pkgToMethodTypeParameter = pkgDivide(mapper.classToMethodTypeParam); + this.pkgToMethodArgTypeParameter = pkgDivide(mapper.classToMethodArgTypeParam); + this.pkgToFieldTypeParameter = pkgDivide(mapper.classToFieldTypeParam); + this.pkgToFieldAnnotations = pkgDivide(mapper.annotationToField); + this.pkgToMethodReturnTypeParameter = pkgDivide(mapper.classToMethodReturnTypeParam); + this.pkgToMethodAnnotations = pkgDivide(mapper.classToMethodAnnotations); + this.pkgToMethodParameterAnnotations = pkgDivide(mapper.classToMethodParamAnnotation); + this.pkgToSubclass = pkgDivide(mapper.classToSubclass); + this.pkgToSubinterface = pkgDivide(mapper.classToSubinterface); + this.pkgToImplementingClass = pkgDivide(mapper.classToImplementingClass); + this.pkgToField = pkgDivide(mapper.classToField); + this.pkgToMethodReturn = pkgDivide(mapper.classToMethodReturn); + this.pkgToMethodArgs = pkgDivide(mapper.classToMethodArgs); + this.pkgToMethodThrows = pkgDivide(mapper.classToMethodThrows); + this.pkgToConstructorAnnotations = pkgDivide(mapper.classToConstructorAnnotations); + this.pkgToConstructorParameterAnnotations = pkgDivide(mapper.classToConstructorParamAnnotation); + this.pkgToConstructorArgs = pkgDivide(mapper.classToConstructorArgs); + this.pkgToConstructorArgTypeParameter = pkgDivide(mapper.classToConstructorArgTypeParam); + this.pkgToConstructorThrows = pkgDivide(mapper.classToConstructorThrows); + //tmp test + if (pkgSet.size() > 0 && + mapper.classToPackage.containsKey(this.typeElement) && + !pkgSet.equals(mapper.classToPackage.get(this.typeElement))) { + configuration.reporter.print(Diagnostic.Kind.WARNING, + "Internal error: package sets don't match: " + + pkgSet + " with: " + mapper.classToPackage.get(this.typeElement)); + } + methodSubWriter = new MethodWriterImpl(this); + constrSubWriter = new ConstructorWriterImpl(this); + fieldSubWriter = new FieldWriterImpl(this); + classSubWriter = new NestedClassWriterImpl(this); + classUseTableSummary = configuration.getText("doclet.Use_Table_Summary", + configuration.getText("doclet.classes")); + subclassUseTableSummary = configuration.getText("doclet.Use_Table_Summary", + configuration.getText("doclet.subclasses")); + subinterfaceUseTableSummary = configuration.getText("doclet.Use_Table_Summary", + configuration.getText("doclet.subinterfaces")); + fieldUseTableSummary = configuration.getText("doclet.Use_Table_Summary", + configuration.getText("doclet.fields")); + methodUseTableSummary = configuration.getText("doclet.Use_Table_Summary", + configuration.getText("doclet.methods")); + constructorUseTableSummary = configuration.getText("doclet.Use_Table_Summary", + configuration.getText("doclet.constructors")); + } + + /** + * Write out class use pages. + * @throws DocletAbortException + */ + public static void generate(ConfigurationImpl configuration, ClassTree classtree) { + ClassUseMapper mapper = new ClassUseMapper(configuration, classtree); + for (TypeElement aClass : configuration.root.getIncludedClasses()) { + // If -nodeprecated option is set and the containing package is marked + // as deprecated, do not generate the class-use page. We will still generate + // the class-use page if the class is marked as deprecated but the containing + // package is not since it could still be linked from that package-use page. + if (!(configuration.nodeprecated && + configuration.utils.isDeprecated(configuration.utils.containingPackage(aClass)))) + ClassUseWriter.generate(configuration, mapper, aClass); + } + for (PackageElement pkg : configuration.packages) { + // If -nodeprecated option is set and the package is marked + // as deprecated, do not generate the package-use page. + if (!(configuration.nodeprecated && configuration.utils.isDeprecated(pkg))) + PackageUseWriter.generate(configuration, mapper, pkg); + } + } + + private Map> pkgDivide(Map> classMap) { + Map> map = new HashMap<>(); + List elements = (List) classMap.get(typeElement); + if (elements != null) { + Collections.sort(elements, utils.makeClassUseComparator()); + for (Element e : elements) { + PackageElement pkg = utils.containingPackage(e); + pkgSet.add(pkg); + List inPkg = map.get(pkg); + if (inPkg == null) { + inPkg = new ArrayList<>(); + map.put(pkg, inPkg); + } + inPkg.add(e); + } + } + return map; + } + + /** + * Generate a class page. + */ + public static void generate(ConfigurationImpl configuration, ClassUseMapper mapper, + TypeElement typeElement) { + ClassUseWriter clsgen; + DocPath path = DocPath.forPackage(configuration.utils, typeElement) + .resolve(DocPaths.CLASS_USE) + .resolve(DocPath.forName(configuration.utils, typeElement)); + try { + clsgen = new ClassUseWriter(configuration, mapper, path, typeElement); + clsgen.generateClassUseFile(); + clsgen.close(); + } catch (IOException exc) { + configuration.standardmessage. + error("doclet.exception_encountered", + exc.toString(), path.getPath()); + throw new DocletAbortException(exc); + } + } + + /** + * Generate the class use elements. + */ + protected void generateClassUseFile() throws IOException { + HtmlTree body = getClassUseHeader(); + HtmlTree div = new HtmlTree(HtmlTag.DIV); + div.addStyle(HtmlStyle.classUseContainer); + if (pkgSet.size() > 0) { + addClassUse(div); + } else { + div.addContent(getResource("doclet.ClassUse_No.usage.of.0", + utils.getFullyQualifiedName(typeElement))); + } + if (configuration.allowTag(HtmlTag.MAIN)) { + mainTree.addContent(div); + body.addContent(mainTree); + } else { + body.addContent(div); + } + HtmlTree htmlTree = (configuration.allowTag(HtmlTag.FOOTER)) + ? HtmlTree.FOOTER() + : body; + addNavLinks(false, htmlTree); + addBottom(htmlTree); + if (configuration.allowTag(HtmlTag.FOOTER)) { + body.addContent(htmlTree); + } + printHtmlDocument(null, true, body); + } + + /** + * Add the class use documentation. + * + * @param contentTree the content tree to which the class use information will be added + */ + protected void addClassUse(Content contentTree) throws IOException { + HtmlTree ul = new HtmlTree(HtmlTag.UL); + ul.addStyle(HtmlStyle.blockList); + if (configuration.packages.size() > 1) { + addPackageList(ul); + addPackageAnnotationList(ul); + } + addClassList(ul); + contentTree.addContent(ul); + } + + /** + * Add the packages elements that use the given class. + * + * @param contentTree the content tree to which the packages elements will be added + */ + protected void addPackageList(Content contentTree) throws IOException { + Content caption = getTableCaption(configuration.getResource( + "doclet.ClassUse_Packages.that.use.0", + getLink(new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.CLASS_USE_HEADER, typeElement)))); + Content table = (configuration.isOutputHtml5()) + ? HtmlTree.TABLE(HtmlStyle.useSummary, caption) + : HtmlTree.TABLE(HtmlStyle.useSummary, useTableSummary, caption); + table.addContent(getSummaryTableHeader(packageTableHeader, "col")); + Content tbody = new HtmlTree(HtmlTag.TBODY); + boolean altColor = true; + for (PackageElement pkg : pkgSet) { + HtmlTree tr = new HtmlTree(HtmlTag.TR); + tr.addStyle(altColor ? HtmlStyle.altColor : HtmlStyle.rowColor); + altColor = !altColor; + addPackageUse(pkg, tr); + tbody.addContent(tr); + } + table.addContent(tbody); + Content li = HtmlTree.LI(HtmlStyle.blockList, table); + contentTree.addContent(li); + } + + /** + * Add the package annotation elements. + * + * @param contentTree the content tree to which the package annotation elements will be added + */ + protected void addPackageAnnotationList(Content contentTree) throws IOException { + if (!utils.isAnnotationType(typeElement) || + pkgToPackageAnnotations == null || + pkgToPackageAnnotations.isEmpty()) { + return; + } + Content caption = getTableCaption(configuration.getResource( + "doclet.ClassUse_PackageAnnotation", + getLink(new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.CLASS_USE_HEADER, typeElement)))); + Content table = (configuration.isOutputHtml5()) + ? HtmlTree.TABLE(HtmlStyle.useSummary, caption) + : HtmlTree.TABLE(HtmlStyle.useSummary, useTableSummary, caption); + table.addContent(getSummaryTableHeader(packageTableHeader, "col")); + Content tbody = new HtmlTree(HtmlTag.TBODY); + boolean altColor = true; + for (PackageElement pkg : pkgToPackageAnnotations) { + HtmlTree tr = new HtmlTree(HtmlTag.TR); + tr.addStyle(altColor ? HtmlStyle.altColor : HtmlStyle.rowColor); + altColor = !altColor; + Content tdFirst = HtmlTree.TD(HtmlStyle.colFirst, getPackageLink(pkg)); + tr.addContent(tdFirst); + HtmlTree tdLast = new HtmlTree(HtmlTag.TD); + tdLast.addStyle(HtmlStyle.colLast); + addSummaryComment(pkg, tdLast); + tr.addContent(tdLast); + tbody.addContent(tr); + } + table.addContent(tbody); + Content li = HtmlTree.LI(HtmlStyle.blockList, table); + contentTree.addContent(li); + } + + /** + * Add the class elements that use the given class. + * + * @param contentTree the content tree to which the class elements will be added + */ + protected void addClassList(Content contentTree) throws IOException { + HtmlTree ul = new HtmlTree(HtmlTag.UL); + ul.addStyle(HtmlStyle.blockList); + for (PackageElement pkg : pkgSet) { + Content markerAnchor = getMarkerAnchor(getPackageAnchorName(pkg)); + HtmlTree htmlTree = (configuration.allowTag(HtmlTag.SECTION)) + ? HtmlTree.SECTION(markerAnchor) + : HtmlTree.LI(HtmlStyle.blockList, markerAnchor); + Content link = getResource("doclet.ClassUse_Uses.of.0.in.1", + getLink(new LinkInfoImpl(configuration, LinkInfoImpl.Kind.CLASS_USE_HEADER, + typeElement)), + getPackageLink(pkg, utils.getPackageName(pkg))); + Content heading = HtmlTree.HEADING(HtmlConstants.SUMMARY_HEADING, link); + htmlTree.addContent(heading); + addClassUse(pkg, htmlTree); + if (configuration.allowTag(HtmlTag.SECTION)) { + ul.addContent(HtmlTree.LI(HtmlStyle.blockList, htmlTree)); + } else { + ul.addContent(htmlTree); + } + } + Content li = HtmlTree.LI(HtmlStyle.blockList, ul); + contentTree.addContent(li); + } + + /** + * Add the package use information. + * + * @param pkg the package that uses the given class + * @param contentTree the content tree to which the package use information will be added + */ + protected void addPackageUse(PackageElement pkg, Content contentTree) throws IOException { + Content tdFirst = HtmlTree.TD(HtmlStyle.colFirst, + getHyperLink(getPackageAnchorName(pkg), new StringContent(utils.getPackageName(pkg)))); + contentTree.addContent(tdFirst); + HtmlTree tdLast = new HtmlTree(HtmlTag.TD); + tdLast.addStyle(HtmlStyle.colLast); + addSummaryComment(pkg, tdLast); + contentTree.addContent(tdLast); + } + + /** + * Add the class use information. + * + * @param pkg the package that uses the given class + * @param contentTree the content tree to which the class use information will be added + */ + protected void addClassUse(PackageElement pkg, Content contentTree) throws IOException { + Content classLink = getLink(new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.CLASS_USE_HEADER, typeElement)); + Content pkgLink = getPackageLink(pkg, utils.getPackageName(pkg)); + classSubWriter.addUseInfo(pkgToClassAnnotations.get(pkg), + configuration.getResource("doclet.ClassUse_Annotation", classLink, + pkgLink), classUseTableSummary, contentTree); + classSubWriter.addUseInfo(pkgToClassTypeParameter.get(pkg), + configuration.getResource("doclet.ClassUse_TypeParameter", classLink, + pkgLink), classUseTableSummary, contentTree); + classSubWriter.addUseInfo(pkgToSubclass.get(pkg), + configuration.getResource("doclet.ClassUse_Subclass", classLink, + pkgLink), subclassUseTableSummary, contentTree); + classSubWriter.addUseInfo(pkgToSubinterface.get(pkg), + configuration.getResource("doclet.ClassUse_Subinterface", classLink, + pkgLink), subinterfaceUseTableSummary, contentTree); + classSubWriter.addUseInfo(pkgToImplementingClass.get(pkg), + configuration.getResource("doclet.ClassUse_ImplementingClass", classLink, + pkgLink), classUseTableSummary, contentTree); + fieldSubWriter.addUseInfo(pkgToField.get(pkg), + configuration.getResource("doclet.ClassUse_Field", classLink, + pkgLink), fieldUseTableSummary, contentTree); + fieldSubWriter.addUseInfo(pkgToFieldAnnotations.get(pkg), + configuration.getResource("doclet.ClassUse_FieldAnnotations", classLink, + pkgLink), fieldUseTableSummary, contentTree); + fieldSubWriter.addUseInfo(pkgToFieldTypeParameter.get(pkg), + configuration.getResource("doclet.ClassUse_FieldTypeParameter", classLink, + pkgLink), fieldUseTableSummary, contentTree); + methodSubWriter.addUseInfo(pkgToMethodAnnotations.get(pkg), + configuration.getResource("doclet.ClassUse_MethodAnnotations", classLink, + pkgLink), methodUseTableSummary, contentTree); + methodSubWriter.addUseInfo(pkgToMethodParameterAnnotations.get(pkg), + configuration.getResource("doclet.ClassUse_MethodParameterAnnotations", classLink, + pkgLink), methodUseTableSummary, contentTree); + methodSubWriter.addUseInfo(pkgToMethodTypeParameter.get(pkg), + configuration.getResource("doclet.ClassUse_MethodTypeParameter", classLink, + pkgLink), methodUseTableSummary, contentTree); + methodSubWriter.addUseInfo(pkgToMethodReturn.get(pkg), + configuration.getResource("doclet.ClassUse_MethodReturn", classLink, + pkgLink), methodUseTableSummary, contentTree); + methodSubWriter.addUseInfo(pkgToMethodReturnTypeParameter.get(pkg), + configuration.getResource("doclet.ClassUse_MethodReturnTypeParameter", classLink, + pkgLink), methodUseTableSummary, contentTree); + methodSubWriter.addUseInfo(pkgToMethodArgs.get(pkg), + configuration.getResource("doclet.ClassUse_MethodArgs", classLink, + pkgLink), methodUseTableSummary, contentTree); + methodSubWriter.addUseInfo(pkgToMethodArgTypeParameter.get(pkg), + configuration.getResource("doclet.ClassUse_MethodArgsTypeParameters", classLink, + pkgLink), methodUseTableSummary, contentTree); + methodSubWriter.addUseInfo(pkgToMethodThrows.get(pkg), + configuration.getResource("doclet.ClassUse_MethodThrows", classLink, + pkgLink), methodUseTableSummary, contentTree); + constrSubWriter.addUseInfo(pkgToConstructorAnnotations.get(pkg), + configuration.getResource("doclet.ClassUse_ConstructorAnnotations", classLink, + pkgLink), constructorUseTableSummary, contentTree); + constrSubWriter.addUseInfo(pkgToConstructorParameterAnnotations.get(pkg), + configuration.getResource("doclet.ClassUse_ConstructorParameterAnnotations", classLink, + pkgLink), constructorUseTableSummary, contentTree); + constrSubWriter.addUseInfo(pkgToConstructorArgs.get(pkg), + configuration.getResource("doclet.ClassUse_ConstructorArgs", classLink, + pkgLink), constructorUseTableSummary, contentTree); + constrSubWriter.addUseInfo(pkgToConstructorArgTypeParameter.get(pkg), + configuration.getResource("doclet.ClassUse_ConstructorArgsTypeParameters", classLink, + pkgLink), constructorUseTableSummary, contentTree); + constrSubWriter.addUseInfo(pkgToConstructorThrows.get(pkg), + configuration.getResource("doclet.ClassUse_ConstructorThrows", classLink, + pkgLink), constructorUseTableSummary, contentTree); + } + + /** + * Get the header for the class use Listing. + * + * @return a content tree representing the class use header + */ + protected HtmlTree getClassUseHeader() { + String cltype = configuration.getText(utils.isInterface(typeElement) + ? "doclet.Interface" + : "doclet.Class"); + String clname = utils.getFullyQualifiedName(typeElement); + String title = configuration.getText("doclet.Window_ClassUse_Header", + cltype, clname); + HtmlTree bodyTree = getBody(true, getWindowTitle(title)); + HtmlTree htmlTree = (configuration.allowTag(HtmlTag.HEADER)) + ? HtmlTree.HEADER() + : bodyTree; + addTop(htmlTree); + addNavLinks(true, htmlTree); + if (configuration.allowTag(HtmlTag.HEADER)) { + bodyTree.addContent(htmlTree); + } + ContentBuilder headContent = new ContentBuilder(); + headContent.addContent(getResource("doclet.ClassUse_Title", cltype)); + headContent.addContent(new HtmlTree(HtmlTag.BR)); + headContent.addContent(clname); + Content heading = HtmlTree.HEADING(HtmlConstants.CLASS_PAGE_HEADING, + true, HtmlStyle.title, headContent); + Content div = HtmlTree.DIV(HtmlStyle.header, heading); + if (configuration.allowTag(HtmlTag.MAIN)) { + mainTree.addContent(div); + } else { + bodyTree.addContent(div); + } + return bodyTree; + } + + /** + * Get this package link. + * + * @return a content tree for the package link + */ + protected Content getNavLinkPackage() { + Content linkContent = + getHyperLink(DocPath.parent.resolve(DocPaths.PACKAGE_SUMMARY), packageLabel); + Content li = HtmlTree.LI(linkContent); + return li; + } + + /** + * Get class page link. + * + * @return a content tree for the class page link + */ + protected Content getNavLinkClass() { + Content linkContent = getLink(new LinkInfoImpl( + configuration, LinkInfoImpl.Kind.CLASS_USE_HEADER, typeElement) + .label(configuration.getText("doclet.Class"))); + Content li = HtmlTree.LI(linkContent); + return li; + } + + /** + * Get the use link. + * + * @return a content tree for the use link + */ + protected Content getNavLinkClassUse() { + Content li = HtmlTree.LI(HtmlStyle.navBarCell1Rev, useLabel); + return li; + } + + /** + * Get the tree link. + * + * @return a content tree for the tree link + */ + protected Content getNavLinkTree() { + Content linkContent = utils.isEnclosingPackageIncluded(typeElement) + ? getHyperLink(DocPath.parent.resolve(DocPaths.PACKAGE_TREE), treeLabel) + : getHyperLink(pathToRoot.resolve(DocPaths.OVERVIEW_TREE), treeLabel); + Content li = HtmlTree.LI(linkContent); + return li; + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ClassWriterImpl.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ClassWriterImpl.java new file mode 100644 index 00000000000..37bc63c57e6 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ClassWriterImpl.java @@ -0,0 +1,759 @@ +/* + * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.IOException; +import java.util.*; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.SimpleElementVisitor8; + +import com.sun.source.doctree.DocTree; +import com.sun.tools.javac.util.DefinedBy; +import com.sun.tools.javac.util.DefinedBy.Api; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.ClassWriter; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.builders.MemberSummaryBuilder; +import jdk.javadoc.internal.doclets.toolkit.taglets.ParamTaglet; +import jdk.javadoc.internal.doclets.toolkit.util.ClassTree; +import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; +import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; +import jdk.javadoc.internal.doclets.toolkit.util.DocletConstants; +import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberMap; +import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberMap.Kind; + +/** + * Generate the Class Information Page. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @see javax.lang.model.element.TypeElement + * @see java.util.Collections + * @see java.util.List + * @see java.util.ArrayList + * @see java.util.HashMap + * + * @author Atul M Dambalkar + * @author Robert Field + * @author Bhavesh Patel (Modified) + */ +public class ClassWriterImpl extends SubWriterHolderWriter implements ClassWriter { + + protected final TypeElement typeElement; + + protected final ClassTree classtree; + + protected final TypeElement prev; + + protected final TypeElement next; + + /** + * @param configuration the configuration data for the doclet + * @param typeElement the class being documented. + * @param prevClass the previous class that was documented. + * @param nextClass the next class being documented. + * @param classTree the class tree for the given class. + * @throws java.io.IOException + */ + public ClassWriterImpl(ConfigurationImpl configuration, TypeElement typeElement, + TypeElement prevClass, TypeElement nextClass, ClassTree classTree) + throws IOException { + super(configuration, DocPath.forClass(configuration.utils, typeElement)); + this.typeElement = typeElement; + configuration.currentTypeElement = typeElement; + this.classtree = classTree; + this.prev = prevClass; + this.next = nextClass; + } + + /** + * Get this package link. + * + * @return a content tree for the package link + */ + @Override + protected Content getNavLinkPackage() { + Content linkContent = getHyperLink(DocPaths.PACKAGE_SUMMARY, + packageLabel); + Content li = HtmlTree.LI(linkContent); + return li; + } + + /** + * Get the class link. + * + * @return a content tree for the class link + */ + @Override + protected Content getNavLinkClass() { + Content li = HtmlTree.LI(HtmlStyle.navBarCell1Rev, classLabel); + return li; + } + + /** + * Get the class use link. + * + * @return a content tree for the class use link + */ + @Override + protected Content getNavLinkClassUse() { + Content linkContent = getHyperLink(DocPaths.CLASS_USE.resolve(filename), useLabel); + Content li = HtmlTree.LI(linkContent); + return li; + } + + /** + * Get link to previous class. + * + * @return a content tree for the previous class link + */ + @Override + public Content getNavLinkPrevious() { + Content li; + if (prev != null) { + Content prevLink = getLink(new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.CLASS, prev) + .label(prevclassLabel).strong(true)); + li = HtmlTree.LI(prevLink); + } + else + li = HtmlTree.LI(prevclassLabel); + return li; + } + + /** + * Get link to next class. + * + * @return a content tree for the next class link + */ + @Override + public Content getNavLinkNext() { + Content li; + if (next != null) { + Content nextLink = getLink(new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.CLASS, next) + .label(nextclassLabel).strong(true)); + li = HtmlTree.LI(nextLink); + } + else + li = HtmlTree.LI(nextclassLabel); + return li; + } + + /** + * {@inheritDoc} + */ + @Override + public Content getHeader(String header) { + HtmlTree bodyTree = getBody(true, getWindowTitle(utils.getSimpleName(typeElement))); + HtmlTree htmlTree = (configuration.allowTag(HtmlTag.HEADER)) + ? HtmlTree.HEADER() + : bodyTree; + addTop(htmlTree); + addNavLinks(true, htmlTree); + if (configuration.allowTag(HtmlTag.HEADER)) { + bodyTree.addContent(htmlTree); + } + bodyTree.addContent(HtmlConstants.START_OF_CLASS_DATA); + HtmlTree div = new HtmlTree(HtmlTag.DIV); + div.addStyle(HtmlStyle.header); + PackageElement pkg = utils.containingPackage(typeElement); + if (!pkg.isUnnamed()) { + Content pkgNameContent = new StringContent(utils.getPackageName(pkg)); + Content pkgNameDiv = HtmlTree.DIV(HtmlStyle.subTitle, pkgNameContent); + div.addContent(pkgNameDiv); + } + LinkInfoImpl linkInfo = new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.CLASS_HEADER, typeElement); + //Let's not link to ourselves in the header. + linkInfo.linkToSelf = false; + Content headerContent = new StringContent(header); + Content heading = HtmlTree.HEADING(HtmlConstants.CLASS_PAGE_HEADING, true, + HtmlStyle.title, headerContent); + heading.addContent(getTypeParameterLinks(linkInfo)); + div.addContent(heading); + if (configuration.allowTag(HtmlTag.MAIN)) { + mainTree.addContent(div); + } else { + bodyTree.addContent(div); + } + return bodyTree; + } + + /** + * {@inheritDoc} + */ + @Override + public Content getClassContentHeader() { + return getContentHeader(); + } + + /** + * {@inheritDoc} + */ + @Override + public void addFooter(Content contentTree) { + contentTree.addContent(HtmlConstants.END_OF_CLASS_DATA); + Content htmlTree = (configuration.allowTag(HtmlTag.FOOTER)) + ? HtmlTree.FOOTER() + : contentTree; + addNavLinks(false, htmlTree); + addBottom(htmlTree); + if (configuration.allowTag(HtmlTag.FOOTER)) { + contentTree.addContent(htmlTree); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void printDocument(Content contentTree) throws IOException { + printHtmlDocument(configuration.metakeywords.getMetaKeywords(typeElement), + true, contentTree); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getClassInfoTreeHeader() { + return getMemberTreeHeader(); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getClassInfo(Content classInfoTree) { + return getMemberTree(HtmlStyle.description, classInfoTree); + } + + /** + * {@inheritDoc} + */ + @Override + public void addClassSignature(String modifiers, Content classInfoTree) { + classInfoTree.addContent(new HtmlTree(HtmlTag.BR)); + Content pre = new HtmlTree(HtmlTag.PRE); + addAnnotationInfo(typeElement, pre); + pre.addContent(modifiers); + LinkInfoImpl linkInfo = new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.CLASS_SIGNATURE, typeElement); + //Let's not link to ourselves in the signature. + linkInfo.linkToSelf = false; + Content className = new StringContent(utils.getSimpleName(typeElement)); + Content parameterLinks = getTypeParameterLinks(linkInfo); + if (configuration.linksource) { + addSrcLink(typeElement, className, pre); + pre.addContent(parameterLinks); + } else { + Content span = HtmlTree.SPAN(HtmlStyle.typeNameLabel, className); + span.addContent(parameterLinks); + pre.addContent(span); + } + if (!utils.isInterface(typeElement)) { + TypeMirror superclass = utils.getFirstVisibleSuperClass(typeElement); + if (superclass != null) { + pre.addContent(DocletConstants.NL); + pre.addContent("extends "); + Content link = getLink(new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.CLASS_SIGNATURE_PARENT_NAME, + superclass)); + pre.addContent(link); + } + } + List interfaces = typeElement.getInterfaces(); + if (!interfaces.isEmpty()) { + boolean isFirst = true; + for (TypeMirror type : interfaces) { + TypeElement tDoc = utils.asTypeElement(type); + if (!(utils.isPublic(tDoc) || utils.isLinkable(tDoc))) { + continue; + } + if (isFirst) { + pre.addContent(DocletConstants.NL); + pre.addContent(utils.isInterface(typeElement) ? "extends " : "implements "); + isFirst = false; + } else { + pre.addContent(", "); + } + Content link = getLink(new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.CLASS_SIGNATURE_PARENT_NAME, + type)); + pre.addContent(link); + } + } + classInfoTree.addContent(pre); + } + + /** + * {@inheritDoc} + */ + @Override + public void addClassDescription(Content classInfoTree) { + if(!configuration.nocomment) { + // generate documentation for the class. + if (!utils.getBody(typeElement).isEmpty()) { + addInlineComment(typeElement, classInfoTree); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void addClassTagInfo(Content classInfoTree) { + if(!configuration.nocomment) { + // Print Information about all the tags here + addTagsInfo(typeElement, classInfoTree); + } + } + + /** + * Get the class hierarchy tree for the given class. + * + * @param type the class to print the hierarchy for + * @return a content tree for class inheritence + */ + private Content getClassInheritenceTree(TypeMirror type) { + TypeMirror sup; + HtmlTree classTreeUl = new HtmlTree(HtmlTag.UL); + classTreeUl.addStyle(HtmlStyle.inheritance); + Content liTree = null; + do { + sup = utils.getFirstVisibleSuperClass(type); + if (sup != null) { + HtmlTree ul = new HtmlTree(HtmlTag.UL); + ul.addStyle(HtmlStyle.inheritance); + ul.addContent(getTreeForClassHelper(type)); + if (liTree != null) + ul.addContent(liTree); + Content li = HtmlTree.LI(ul); + liTree = li; + type = sup; + } else + classTreeUl.addContent(getTreeForClassHelper(type)); + } while (sup != null); + if (liTree != null) + classTreeUl.addContent(liTree); + return classTreeUl; + } + + /** + * Get the class helper tree for the given class. + * + * @param type the class to print the helper for + * @return a content tree for class helper + */ + private Content getTreeForClassHelper(TypeMirror type) { + Content li = new HtmlTree(HtmlTag.LI); + if (type.equals(typeElement.asType())) { + Content typeParameters = getTypeParameterLinks( + new LinkInfoImpl(configuration, LinkInfoImpl.Kind.TREE, + typeElement)); + if (configuration.shouldExcludeQualifier(utils.containingPackage(typeElement).toString())) { + li.addContent(utils.asTypeElement(type).getSimpleName().toString()); + li.addContent(typeParameters); + } else { + li.addContent(utils.asTypeElement(type).getQualifiedName().toString()); + li.addContent(typeParameters); + } + } else { + Content link = getLink(new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.CLASS_TREE_PARENT, type) + .label(configuration.getClassName(utils.asTypeElement(type)))); + li.addContent(link); + } + return li; + } + + /** + * {@inheritDoc} + */ + @Override + public void addClassTree(Content classContentTree) { + if (!utils.isClass(typeElement)) { + return; + } + classContentTree.addContent(getClassInheritenceTree(typeElement.asType())); + } + + /** + * {@inheritDoc} + */ + @Override + public void addTypeParamInfo(Content classInfoTree) { + if (!utils.getTypeParamTrees(typeElement).isEmpty()) { + Content typeParam = (new ParamTaglet()).getTagletOutput(typeElement, + getTagletWriterInstance(false)); + Content dl = HtmlTree.DL(typeParam); + classInfoTree.addContent(dl); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void addSubClassInfo(Content classInfoTree) { + if (utils.isClass(typeElement)) { + if (typeElement.getQualifiedName().toString().equals("java.lang.Object") || + typeElement.getQualifiedName().toString().equals("org.omg.CORBA.Object")) { + return; // Don't generate the list, too huge + } + Set subclasses = classtree.directSubClasses(typeElement, false); + if (!subclasses.isEmpty()) { + Content label = getResource( + "doclet.Subclasses"); + Content dt = HtmlTree.DT(label); + Content dl = HtmlTree.DL(dt); + dl.addContent(getClassLinks(LinkInfoImpl.Kind.SUBCLASSES, + subclasses)); + classInfoTree.addContent(dl); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void addSubInterfacesInfo(Content classInfoTree) { + if (utils.isInterface(typeElement)) { + Set subInterfaces = classtree.allSubClasses(typeElement, false); + if (!subInterfaces.isEmpty()) { + Content label = getResource( + "doclet.Subinterfaces"); + Content dt = HtmlTree.DT(label); + Content dl = HtmlTree.DL(dt); + dl.addContent(getClassLinks(LinkInfoImpl.Kind.SUBINTERFACES, + subInterfaces)); + classInfoTree.addContent(dl); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void addInterfaceUsageInfo (Content classInfoTree) { + if (!utils.isInterface(typeElement)) { + return; + } + if (typeElement.getQualifiedName().toString().equals("java.lang.Cloneable") || + typeElement.getQualifiedName().toString().equals("java.io.Serializable")) { + return; // Don't generate the list, too big + } + Set implcl = classtree.implementingClasses(typeElement); + if (!implcl.isEmpty()) { + Content label = getResource( + "doclet.Implementing_Classes"); + Content dt = HtmlTree.DT(label); + Content dl = HtmlTree.DL(dt); + dl.addContent(getClassLinks(LinkInfoImpl.Kind.IMPLEMENTED_CLASSES, + implcl)); + classInfoTree.addContent(dl); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void addImplementedInterfacesInfo(Content classInfoTree) { + SortedSet interfaces = new TreeSet<>(utils.makeTypeMirrorClassUseComparator()); + interfaces.addAll(utils.getAllInterfaces(typeElement)); + if (utils.isClass(typeElement) && !interfaces.isEmpty()) { + Content label = getResource( + "doclet.All_Implemented_Interfaces"); + Content dt = HtmlTree.DT(label); + Content dl = HtmlTree.DL(dt); + dl.addContent(getClassLinks(LinkInfoImpl.Kind.IMPLEMENTED_INTERFACES, interfaces)); + classInfoTree.addContent(dl); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void addSuperInterfacesInfo(Content classInfoTree) { + SortedSet interfaces = + new TreeSet<>(utils.makeTypeMirrorIndexUseComparator()); + interfaces.addAll(utils.getAllInterfaces(typeElement)); + + if (utils.isInterface(typeElement) && !interfaces.isEmpty()) { + Content label = getResource("doclet.All_Superinterfaces"); + Content dt = HtmlTree.DT(label); + Content dl = HtmlTree.DL(dt); + dl.addContent(getClassLinks(LinkInfoImpl.Kind.SUPER_INTERFACES, interfaces)); + classInfoTree.addContent(dl); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void addNestedClassInfo(final Content classInfoTree) { + Element outerClass = typeElement.getEnclosingElement(); + if (outerClass == null) + return; + new SimpleElementVisitor8() { + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Void visitType(TypeElement e, Void p) { + String label = utils.isInterface(e) + ? "doclet.Enclosing_Interface" + : "doclet.Enclosing_Class"; + Content dt = HtmlTree.DT(getResource(label)); + Content dl = HtmlTree.DL(dt); + Content dd = new HtmlTree(HtmlTag.DD); + dd.addContent(getLink(new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.CLASS, e))); + dl.addContent(dd); + classInfoTree.addContent(dl); + return null; + } + }.visit(outerClass); + } + + /** + * {@inheritDoc} + */ + @Override + public void addFunctionalInterfaceInfo (Content classInfoTree) { + if (isFunctionalInterface()) { + Content dt = HtmlTree.DT(getResource("doclet.Functional_Interface")); + Content dl = HtmlTree.DL(dt); + Content dd = new HtmlTree(HtmlTag.DD); + dd.addContent(getResource("doclet.Functional_Interface_Message")); + dl.addContent(dd); + classInfoTree.addContent(dl); + } + } + + public boolean isFunctionalInterface() { + List annotationMirrors = ((Element) typeElement).getAnnotationMirrors(); + for (AnnotationMirror anno : annotationMirrors) { + if (utils.isFunctionalInterface(anno)) { + return true; + } + } + return false; + } + + + /** + * {@inheritDoc} + */ + @Override + public void addClassDeprecationInfo(Content classInfoTree) { + Content hr = new HtmlTree(HtmlTag.HR); + classInfoTree.addContent(hr); + List deprs = utils.getBlockTags(typeElement, DocTree.Kind.DEPRECATED); + if (utils.isDeprecated(typeElement)) { + Content deprLabel = HtmlTree.SPAN(HtmlStyle.deprecatedLabel, deprecatedPhrase); + Content div = HtmlTree.DIV(HtmlStyle.block, deprLabel); + if (!deprs.isEmpty()) { + CommentHelper ch = utils.getCommentHelper(typeElement); + DocTree dt = deprs.get(0); + List commentTags = ch.getBody(configuration, dt); + if (!commentTags.isEmpty()) { + div.addContent(getSpace()); + addInlineDeprecatedComment(typeElement, deprs.get(0), div); + } + } + classInfoTree.addContent(div); + } + } + + /** + * Get links to the given classes. + * + * @param context the id of the context where the link will be printed + * @param list the list of classes + * @return a content tree for the class list + */ + private Content getClassLinks(LinkInfoImpl.Kind context, Collection list) { + Content dd = new HtmlTree(HtmlTag.DD); + boolean isFirst = true; + for (Object type : list) { + if (!isFirst) { + Content separator = new StringContent(", "); + dd.addContent(separator); + } else { + isFirst = false; + } + // TODO: should we simply split this method up to avoid instanceof ? + if (type instanceof TypeElement) { + Content link = getLink( + new LinkInfoImpl(configuration, context, (TypeElement)(type))); + dd.addContent(link); + } else { + Content link = getLink( + new LinkInfoImpl(configuration, context, ((TypeMirror)type))); + dd.addContent(link); + } + } + return dd; + } + + /** + * {@inheritDoc} + */ + @Override + protected Content getNavLinkTree() { + Content treeLinkContent = getHyperLink(DocPaths.PACKAGE_TREE, + treeLabel, "", ""); + Content li = HtmlTree.LI(treeLinkContent); + return li; + } + + /** + * Add summary details to the navigation bar. + * + * @param subDiv the content tree to which the summary detail links will be added + */ + protected void addSummaryDetailLinks(Content subDiv) { + try { + Content div = HtmlTree.DIV(getNavSummaryLinks()); + div.addContent(getNavDetailLinks()); + subDiv.addContent(div); + } catch (Exception e) { + throw new DocletAbortException(e); + } + } + + /** + * Get summary links for navigation bar. + * + * @return the content tree for the navigation summary links + */ + protected Content getNavSummaryLinks() throws Exception { + Content li = HtmlTree.LI(summaryLabel); + li.addContent(getSpace()); + Content ulNav = HtmlTree.UL(HtmlStyle.subNavList, li); + MemberSummaryBuilder memberSummaryBuilder = (MemberSummaryBuilder) + configuration.getBuilderFactory().getMemberSummaryBuilder(this); + for (VisibleMemberMap.Kind kind : VisibleMemberMap.Kind.summarySet) { + Content liNav = new HtmlTree(HtmlTag.LI); + if (kind == VisibleMemberMap.Kind.ENUM_CONSTANTS && !utils.isEnum(typeElement)) { + continue; + } + if (kind == VisibleMemberMap.Kind.CONSTRUCTORS && utils.isEnum(typeElement)) { + continue; + } + AbstractMemberWriter writer = + ((AbstractMemberWriter) memberSummaryBuilder.getMemberSummaryWriter(kind)); + if (writer == null) { + liNav.addContent(getResource(VisibleMemberMap.Kind.getNavLinkLabels(kind))); + } else { + writer.addNavSummaryLink( + memberSummaryBuilder.members(kind), + memberSummaryBuilder.getVisibleMemberMap(kind), liNav); + } + if (kind != Kind.METHODS) { + addNavGap(liNav); + } + ulNav.addContent(liNav); + } + return ulNav; + } + + /** + * Get detail links for the navigation bar. + * + * @return the content tree for the detail links + * @throws java.lang.Exception + */ + protected Content getNavDetailLinks() throws Exception { + Content li = HtmlTree.LI(detailLabel); + li.addContent(getSpace()); + Content ulNav = HtmlTree.UL(HtmlStyle.subNavList, li); + MemberSummaryBuilder memberSummaryBuilder = (MemberSummaryBuilder) + configuration.getBuilderFactory().getMemberSummaryBuilder(this); + for (VisibleMemberMap.Kind kind : VisibleMemberMap.Kind.detailSet) { + Content liNav = new HtmlTree(HtmlTag.LI); + AbstractMemberWriter writer = + ((AbstractMemberWriter) memberSummaryBuilder. + getMemberSummaryWriter(kind)); + if (kind == VisibleMemberMap.Kind.ENUM_CONSTANTS && !utils.isEnum(typeElement)) { + continue; + } + if (kind == VisibleMemberMap.Kind.CONSTRUCTORS && utils.isEnum(typeElement)) { + continue; + } + if (writer == null) { + liNav.addContent(getResource(VisibleMemberMap.Kind.getNavLinkLabels(kind))); + } else { + writer.addNavDetailLink(memberSummaryBuilder.members(kind), liNav); + } + if (kind != Kind.METHODS) { + addNavGap(liNav); + } + ulNav.addContent(liNav); + } + return ulNav; + } + + /** + * Add gap between navigation bar elements. + * + * @param liNav the content tree to which the gap will be added + */ + protected void addNavGap(Content liNav) { + liNav.addContent(getSpace()); + liNav.addContent("|"); + liNav.addContent(getSpace()); + } + + /** + * Return the TypeElement being documented. + * + * @return the TypeElement being documented. + */ + @Override + public TypeElement getTypeElement() { + return typeElement; + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ConfigurationImpl.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ConfigurationImpl.java new file mode 100644 index 00000000000..0b7aa5ec023 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ConfigurationImpl.java @@ -0,0 +1,747 @@ +/* + * Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.net.*; +import java.util.*; + +import javax.lang.model.element.Element; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; + +import com.sun.source.util.DocTreePath; +import com.sun.tools.doclint.DocLint; + +import jdk.javadoc.doclet.Doclet; +import jdk.javadoc.doclet.DocletEnvironment; +import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlVersion; +import jdk.javadoc.internal.doclets.toolkit.Configuration; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.WriterFactory; +import jdk.javadoc.internal.doclets.toolkit.util.DocFile; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; +import jdk.javadoc.internal.doclets.toolkit.util.MessageRetriever; +import jdk.javadoc.internal.doclets.toolkit.util.Utils; + +import static javax.tools.Diagnostic.Kind.*; + +/** + * Configure the output based on the command line options. + *

+ * Also determine the length of the command line option. For example, + * for a option "-header" there will be a string argument associated, then the + * the length of option "-header" is two. But for option "-nohelp" no argument + * is needed so it's length is 1. + *

+ *

+ * Also do the error checking on the options used. For example it is illegal to + * use "-helpfile" option when already "-nohelp" option is used. + *

+ * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Robert Field. + * @author Atul Dambalkar. + * @author Jamie Ho + * @author Bhavesh Patel (Modified) + */ +public class ConfigurationImpl extends Configuration { + + /** + * The build date. Note: For now, we will use + * a version number instead of a date. + */ + public static final String BUILD_DATE = System.getProperty("java.version"); + + /** + * Argument for command line option "-header". + */ + public String header = ""; + + /** + * Argument for command line option "-packagesheader". + */ + public String packagesheader = ""; + + /** + * Argument for command line option "-footer". + */ + public String footer = ""; + + /** + * Argument for command line option "-doctitle". + */ + public String doctitle = ""; + + /** + * Argument for command line option "-windowtitle". + */ + public String windowtitle = ""; + + /** + * Argument for command line option "-top". + */ + public String top = ""; + + /** + * Argument for command line option "-bottom". + */ + public String bottom = ""; + + /** + * Argument for command line option "-helpfile". + */ + public String helpfile = ""; + + /** + * Argument for command line option "-stylesheetfile". + */ + public String stylesheetfile = ""; + + /** + * Argument for command line option "-Xdocrootparent". + */ + public String docrootparent = ""; + + public boolean sortedMethodDetails = false; + + /** + * True if command line option "-nohelp" is used. Default value is false. + */ + public boolean nohelp = false; + + /** + * True if command line option "-splitindex" is used. Default value is + * false. + */ + public boolean splitindex = false; + + /** + * False if command line option "-noindex" is used. Default value is true. + */ + public boolean createindex = true; + + /** + * True if command line option "-use" is used. Default value is false. + */ + public boolean classuse = false; + + /** + * False if command line option "-notree" is used. Default value is true. + */ + public boolean createtree = true; + + /** + * True if command line option "-nodeprecated" is used. Default value is + * false. + */ + public boolean nodeprecatedlist = false; + + /** + * True if command line option "-nonavbar" is used. Default value is false. + */ + public boolean nonavbar = false; + + /** + * True if command line option "-nooverview" is used. Default value is + * false + */ + private boolean nooverview = false; + + /** + * The overview path specified with "-overview" flag. + */ + public String overviewpath = null; + + /** + * This is true if option "-overview" is used or option "-overview" is not + * used and number of packages is more than one. + */ + public boolean createoverview = false; + + /** + * This is the HTML version of the generated pages. HTML 4.01 is the default output version. + */ + public HtmlVersion htmlVersion = HtmlVersion.HTML4; + + /** + * Collected set of doclint options + */ + public Map doclintOpts = new LinkedHashMap<>(); + + /** + * Unique Resource Handler for this package. + */ + public final MessageRetriever standardmessage; + + /** + * First file to appear in the right-hand frame in the generated + * documentation. + */ + public DocPath topFile = DocPath.empty; + + /** + * The TypeElement for the class file getting generated. + */ + public TypeElement currentTypeElement = null; // Set this TypeElement in the ClassWriter. + + protected List memberSearchIndex = new ArrayList<>(); + + protected List packageSearchIndex = new ArrayList<>(); + + protected List tagSearchIndex = new ArrayList<>(); + + protected List typeSearchIndex = new ArrayList<>(); + + protected Map> tagSearchIndexMap = new HashMap<>(); + + protected Set tagSearchIndexKeys; + + /** + * Constructor. Initializes resource for the + * {@link com.sun.tools.doclets.internal.toolkit.util.MessageRetriever MessageRetriever}. + */ + public ConfigurationImpl() { + standardmessage = new MessageRetriever(this, + "jdk.javadoc.internal.doclets.formats.html.resources.standard"); + } + + private final String versionRBName = "jdk.javadoc.internal.tool.resources.version"; + private ResourceBundle versionRB; + + /** + * Return the build date for the doclet. + * @return the build date + */ + @Override + public String getDocletSpecificBuildDate() { + if (versionRB == null) { + try { + versionRB = ResourceBundle.getBundle(versionRBName, getLocale()); + } catch (MissingResourceException e) { + return BUILD_DATE; + } + } + + try { + return versionRB.getString("release"); + } catch (MissingResourceException e) { + return BUILD_DATE; + } + } + + protected boolean validateOptions() { + // check shared options + if (!generalValidOptions()) { + return false; + } + boolean helpfileSeen = false; + // otherwise look at our options + for (Doclet.Option opt : optionsProcessed) { + if (opt.matches("-helpfile")) { + if (nohelp == true) { + reporter.print(ERROR, getText("doclet.Option_conflict", + "-helpfile", "-nohelp")); + return false; + } + if (helpfileSeen) { + reporter.print(ERROR, getText("doclet.Option_reuse", + "-helpfile")); + return false; + } + helpfileSeen = true; + DocFile help = DocFile.createFileForInput(this, helpfile); + if (!help.exists()) { + reporter.print(ERROR, getText("doclet.File_not_found", helpfile)); + return false; + } + } else if (opt.matches("-nohelp")) { + if (helpfileSeen) { + reporter.print(ERROR, getText("doclet.Option_conflict", + "-nohelp", "-helpfile")); + return false; + } + } else if (opt.matches("-xdocrootparent")) { + try { + URL ignored = new URL(docrootparent); + } catch (MalformedURLException e) { + reporter.print(ERROR, getText("doclet.MalformedURL", docrootparent)); + return false; + } + } else if (opt.matches("-overview")) { + if (nooverview == true) { + reporter.print(ERROR, getText("doclet.Option_conflict", + "-overview", "-nooverview")); + return false; + } + } else if (opt.matches("-nooverview")) { + if (overviewpath != null) { + reporter.print(ERROR, getText("doclet.Option_conflict", + "-nooverview", "-overview")); + return false; + } + } else if (opt.matches("-splitindex")) { + if (createindex == false) { + reporter.print(ERROR, getText("doclet.Option_conflict", + "-splitindex", "-noindex")); + return false; + } + } else if (opt.matches("-noindex")) { + if (splitindex == true) { + reporter.print(ERROR, getText("doclet.Option_conflict", + "-noindex", "-splitindex")); + return false; + } + } else if (opt.matches("-xdoclint:")) { + String dopt = doclintOpts.get(opt); + if (dopt == null) { + continue; + } + if (dopt.contains("/")) { + reporter.print(ERROR, getText("doclet.Option_doclint_no_qualifiers")); + return false; + } + if (!DocLint.isValidOption(dopt)) { + reporter.print(ERROR, getText("doclet.Option_doclint_invalid_arg")); + return false; + } + } else if (opt.matches("-xdoclint/package:")) { + String dopt = doclintOpts.get(opt); + if (!DocLint.isValidOption(dopt)) { + reporter.print(ERROR, getText("doclet.Option_doclint_package_invalid_arg")); + return false; + } + } + } + return true; + } + + @Override + public boolean finishOptionSettings() { + if (!validateOptions()) { + return false; + } + if (!root.getSpecifiedElements().isEmpty()) { + Map map = new HashMap<>(); + PackageElement pkg; + List classes = new ArrayList<>(root.getIncludedClasses()); + for (TypeElement aClass : classes) { + pkg = utils.containingPackage(aClass); + if (!map.containsKey(utils.getPackageName(pkg))) { + map.put(utils.getPackageName(pkg), pkg); + } + } + } + setCreateOverview(); + setTopFile(root); + workArounds.initDocLint(doclintOpts.values(), tagletManager.getCustomTagNames(), + Utils.toLowerCase(htmlVersion.name())); + return true; + } + + /** + * Return true if the generated output is HTML5. + */ + public boolean isOutputHtml5() { + return htmlVersion == HtmlVersion.HTML5; + } + + /** + * Return true if the tag is allowed for this specific version of HTML. + */ + public boolean allowTag(HtmlTag htmlTag) { + return htmlTag.allowTag(this.htmlVersion); + } + + /** + * {@inheritDoc} + */ + @Override + public MessageRetriever getDocletSpecificMsg() { + return standardmessage; + } + + /** + * Decide the page which will appear first in the right-hand frame. It will + * be "overview-summary.html" if "-overview" option is used or no + * "-overview" but the number of packages is more than one. It will be + * "package-summary.html" of the respective package if there is only one + * package to document. It will be a class page(first in the sorted order), + * if only classes are provided on the command line. + * + * @param root Root of the program structure. + */ + protected void setTopFile(DocletEnvironment root) { + if (!checkForDeprecation(root)) { + return; + } + if (createoverview) { + topFile = DocPaths.OVERVIEW_SUMMARY; + } else { + if (packages.size() == 1 && packages.first().isUnnamed()) { + if (!root.getIncludedClasses().isEmpty()) { + List classes = new ArrayList<>(root.getIncludedClasses()); + TypeElement te = getValidClass(classes); + topFile = DocPath.forClass(utils, te); + } + } else if (!packages.isEmpty()) { + topFile = DocPath.forPackage(packages.first()).resolve(DocPaths.PACKAGE_SUMMARY); + } + } + } + + protected TypeElement getValidClass(List classes) { + if (!nodeprecated) { + return classes.get(0); + } + for (TypeElement te : classes) { + if (!utils.isDeprecated(te)) { + return te; + } + } + return null; + } + + protected boolean checkForDeprecation(DocletEnvironment root) { + for (TypeElement te : root.getIncludedClasses()) { + if (isGeneratedDoc(te)) { + return true; + } + } + return false; + } + + /** + * Generate "overview.html" page if option "-overview" is used or number of + * packages is more than one. Sets {@link #createoverview} field to true. + */ + protected void setCreateOverview() { + if ((overviewpath != null || packages.size() > 1) && !nooverview) { + createoverview = true; + } + } + + /** + * {@inheritDoc} + */ + @Override + public WriterFactory getWriterFactory() { + return new WriterFactoryImpl(this); + } + + /** + * {@inheritDoc} + */ + @Override + public Locale getLocale() { + if (locale == null) + return Locale.getDefault(); + return locale; + } + + /** + * Return the path of the overview file and null if it does not exist. + * + * @return the path of the overview file and null if it does not exist. + */ + public JavaFileObject getOverviewPath() { + if (overviewpath != null && getFileManager() instanceof StandardJavaFileManager) { + StandardJavaFileManager fm = (StandardJavaFileManager) getFileManager(); + return fm.getJavaFileObjects(overviewpath).iterator().next(); + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public JavaFileManager getFileManager() { + return root.getJavaFileManager(); + } + + @Override + public boolean showMessage(DocTreePath path, String key) { + return (path == null || workArounds.haveDocLint()); + } + + @Override + public boolean showMessage(Element e, String key) { + return (e == null || workArounds.haveDocLint()); + } + + @Override + public Content newContent() { + return new ContentBuilder(); + } + + protected void buildSearchTagIndex() { + for (SearchIndexItem sii : tagSearchIndex) { + String tagLabel = sii.getLabel(); + Character unicode = (tagLabel.length() == 0) + ? '*' + : Character.toUpperCase(tagLabel.charAt(0)); + List list = tagSearchIndexMap.get(unicode); + if (list == null) { + list = new ArrayList<>(); + tagSearchIndexMap.put(unicode, list); + } + list.add(sii); + } + tagSearchIndexKeys = tagSearchIndexMap.keySet(); + } + + @Override + public Set getSupportedOptions() { + Doclet.Option[] options = { + new Option(this, "bottom", 1) { + @Override + public boolean process(String opt, ListIterator args) { + optionsProcessed.add(this); + bottom = args.next(); + return true; + } + }, + new Option(this, "charset", 1) { + @Override + public boolean process(String opt, ListIterator args) { + optionsProcessed.add(this); + charset = args.next(); + return true; + } + }, + new Option(this, "doctitle", 1) { + @Override + public boolean process(String opt, ListIterator args) { + optionsProcessed.add(this); + doctitle = args.next(); + return true; + } + }, + new Option(this, "footer", 1) { + @Override + public boolean process(String opt, ListIterator args) { + optionsProcessed.add(this); + footer = args.next(); + return true; + } + }, + new Option(this, "header", 1) { + @Override + public boolean process(String opt, ListIterator args) { + optionsProcessed.add(this); + header = args.next(); + return true; + } + }, + new Option(this, "helpfile", 1) { + @Override + public boolean process(String opt, ListIterator args) { + optionsProcessed.add(this); + helpfile = args.next(); + return true; + } + }, + new Option(this, "html4") { + @Override + public boolean process(String opt, ListIterator args) { + optionsProcessed.add(this); + htmlVersion = HtmlVersion.HTML4; + return true; + } + }, + new Option(this, "html5") { + @Override + public boolean process(String opt, ListIterator args) { + optionsProcessed.add(this); + htmlVersion = HtmlVersion.HTML5; + return true; + } + }, + new Option(this, "nohelp") { + @Override + public boolean process(String opt, ListIterator args) { + optionsProcessed.add(this); + nohelp = true; + return true; + } + }, + new Option(this, "nodeprecatedlist") { + @Override + public boolean process(String opt, ListIterator args) { + optionsProcessed.add(this); + nodeprecatedlist = true; + return true; + } + }, + new Option(this, "noindex") { + @Override + public boolean process(String opt, ListIterator args) { + optionsProcessed.add(this); + createindex = false; + return true; + } + }, + new Option(this, "nonavbar") { + @Override + public boolean process(String opt, ListIterator args) { + optionsProcessed.add(this); + nonavbar = true; + return true; + } + }, + new Hidden(this, "nooverview") { + @Override + public boolean process(String opt, ListIterator args) { + optionsProcessed.add(this); + nooverview = true; + return true; + } + }, + new Option(this, "notree") { + @Override + public boolean process(String opt, ListIterator args) { + optionsProcessed.add(this); + createtree = false; + return true; + } + }, + new Hidden(this, "overview", 1) { + @Override + public boolean process(String opt, ListIterator args) { + optionsProcessed.add(this); + overviewpath = args.next(); + return true; + } + }, + new Hidden(this, "packagesheader", 1) { + @Override + public boolean process(String opt, ListIterator args) { + optionsProcessed.add(this); + packagesheader = args.next(); + return true; + } + }, + new Option(this, "splitindex") { + @Override + public boolean process(String opt, ListIterator args) { + optionsProcessed.add(this); + splitindex = true; + return true; + } + }, + new Option(this, "stylesheetfile", 1) { + @Override + public boolean process(String opt, ListIterator args) { + optionsProcessed.add(this); + stylesheetfile = args.next(); + return true; + } + }, + new Option(this, "top", 1) { + @Override + public boolean process(String opt, ListIterator args) { + optionsProcessed.add(this); + top = args.next(); + return true; + } + }, + new Option(this, "use") { + @Override + public boolean process(String opt, ListIterator args) { + optionsProcessed.add(this); + classuse = true; + return true; + } + }, + new Option(this, "windowtitle", 1) { + @Override + public boolean process(String opt, ListIterator args) { + optionsProcessed.add(this); + windowtitle = args.next().replaceAll("\\<.*?>", ""); + return true; + } + }, + new XOption(this, "xdoclint") { + @Override + public boolean process(String opt, ListIterator args) { + optionsProcessed.add(this); + doclintOpts.put(this, DocLint.XMSGS_OPTION); + return true; + } + }, + new XOption(this, "Xdocrootparent", 1) { + @Override + public boolean process(String opt, ListIterator args) { + optionsProcessed.add(this); + docrootparent = args.next(); + return true; + } + }, + new XOption(this, "doclet.xusage.xdoclint-extended.", "Xdoclint:", 0) { + @Override + public boolean matches(String option) { + String opt = option.startsWith("-") ? option.substring(1) : option; + return opt.toLowerCase().startsWith(getName().toLowerCase()); + } + + @Override + public boolean process(String opt, ListIterator args) { + optionsProcessed.add(this); + doclintOpts.put(this, opt.replace("-Xdoclint:", DocLint.XMSGS_CUSTOM_PREFIX)); + return true; + } + }, + new XOption(this, "doclet.xusage.xdoclint-package.", "Xdoclint/package:", 0) { + @Override + public boolean matches(String option) { + String opt = option.startsWith("-") ? option.substring(1) : option; + return opt.toLowerCase().startsWith(getName().toLowerCase()); + } + + @Override + public boolean process(String opt, ListIterator args) { + optionsProcessed.add(this); + doclintOpts.put(this, opt.replace("-Xdoclint/package:", DocLint.XCHECK_PACKAGE)); + return true; + } + } + }; + Set oset = new TreeSet<>(); + oset.addAll(Arrays.asList(options)); + oset.addAll(super.getSupportedOptions()); + return oset; + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ConstantsSummaryWriterImpl.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ConstantsSummaryWriterImpl.java new file mode 100644 index 00000000000..2b993595998 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ConstantsSummaryWriterImpl.java @@ -0,0 +1,388 @@ +/* + * Copyright (c) 2001, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; +import java.util.*; + +import javax.lang.model.element.Modifier; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; + +import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.ConstantsSummaryWriter; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.DocLink; +import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; + + +/** + * Write the Constants Summary Page in HTML format. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Jamie Ho + * @author Bhavesh Patel (Modified) + */ +public class ConstantsSummaryWriterImpl extends HtmlDocletWriter implements ConstantsSummaryWriter { + + /** + * The configuration used in this run of the standard doclet. + */ + ConfigurationImpl configuration; + + /** + * The current class being documented. + */ + private TypeElement currentTypeElement; + + private final String constantsTableSummary; + + private final List constantsTableHeader; + + /** + * The HTML tree for main tag. + */ + private HtmlTree mainTree = HtmlTree.MAIN(); + + /** + * The HTML tree for constant values summary. + */ + private HtmlTree summaryTree; + + /** + * Construct a ConstantsSummaryWriter. + * @param configuration the configuration used in this run + * of the standard doclet. + */ + public ConstantsSummaryWriterImpl(ConfigurationImpl configuration) + throws IOException { + super(configuration, DocPaths.CONSTANT_VALUES); + this.configuration = configuration; + constantsTableSummary = configuration.getText("doclet.Constants_Table_Summary", + configuration.getText("doclet.Constants_Summary")); + constantsTableHeader = new ArrayList<>(); + constantsTableHeader.add(getModifierTypeHeader()); + constantsTableHeader.add(configuration.getText("doclet.ConstantField")); + constantsTableHeader.add(configuration.getText("doclet.Value")); + } + + /** + * {@inheritDoc} + */ + public Content getHeader() { + String label = configuration.getText("doclet.Constants_Summary"); + HtmlTree bodyTree = getBody(true, getWindowTitle(label)); + HtmlTree htmlTree = (configuration.allowTag(HtmlTag.HEADER)) + ? HtmlTree.HEADER() + : bodyTree; + addTop(htmlTree); + addNavLinks(true, htmlTree); + if (configuration.allowTag(HtmlTag.HEADER)) { + bodyTree.addContent(htmlTree); + } + return bodyTree; + } + + /** + * {@inheritDoc} + */ + public Content getContentsHeader() { + return new HtmlTree(HtmlTag.UL); + } + + /** + * {@inheritDoc} + */ + public void addLinkToPackageContent(PackageElement pkg, + Set printedPackageHeaders, Content contentListTree) { + //add link to summary + Content link; + if (pkg.isUnnamed()) { + link = getHyperLink(getDocLink( + SectionName.UNNAMED_PACKAGE_ANCHOR), + defaultPackageLabel, "", ""); + } else { + String parsedPackageName = utils.parsePackageName(pkg); + Content packageNameContent = getPackageLabel(parsedPackageName); + packageNameContent.addContent(".*"); + link = getHyperLink(DocLink.fragment(parsedPackageName), + packageNameContent, "", ""); + PackageElement abbrevPkg = utils.elementUtils.getPackageElement(parsedPackageName); + printedPackageHeaders.add(abbrevPkg); + } + contentListTree.addContent(HtmlTree.LI(link)); + } + + /** + * {@inheritDoc} + */ + public void addContentsList(Content contentTree, Content contentListTree) { + Content titleContent = getResource( + "doclet.Constants_Summary"); + Content pHeading = HtmlTree.HEADING(HtmlConstants.TITLE_HEADING, true, + HtmlStyle.title, titleContent); + Content div = HtmlTree.DIV(HtmlStyle.header, pHeading); + Content headingContent = getResource( + "doclet.Contents"); + Content heading = HtmlTree.HEADING(HtmlConstants.CONTENT_HEADING, true, + headingContent); + if (configuration.allowTag(HtmlTag.SECTION)) { + HtmlTree section = HtmlTree.SECTION(heading); + section.addContent(contentListTree); + div.addContent(section); + mainTree.addContent(div); + } else { + div.addContent(heading); + div.addContent(contentListTree); + contentTree.addContent(div); + } + } + + /** + * {@inheritDoc} + */ + public Content getConstantSummaries() { + HtmlTree summariesDiv = new HtmlTree(HtmlTag.DIV); + summariesDiv.addStyle(HtmlStyle.constantValuesContainer); + return summariesDiv; + } + + /** + * {@inheritDoc} + */ + public void addPackageName(PackageElement pkg, Content summariesTree, boolean first) { + Content pkgNameContent; + if (!first && configuration.allowTag(HtmlTag.SECTION)) { + summariesTree.addContent(summaryTree); + } + if (pkg.isUnnamed()) { + summariesTree.addContent(getMarkerAnchor( + SectionName.UNNAMED_PACKAGE_ANCHOR)); + pkgNameContent = defaultPackageLabel; + } else { + String parsedPackageName = utils.parsePackageName(pkg); + summariesTree.addContent(getMarkerAnchor(parsedPackageName)); + pkgNameContent = getPackageLabel(parsedPackageName); + } + Content headingContent = new StringContent(".*"); + Content heading = HtmlTree.HEADING(HtmlConstants.PACKAGE_HEADING, true, + pkgNameContent); + heading.addContent(headingContent); + if (configuration.allowTag(HtmlTag.SECTION)) { + summaryTree = HtmlTree.SECTION(heading); + } else { + summariesTree.addContent(heading); + } + } + + /** + * {@inheritDoc} + */ + public Content getClassConstantHeader() { + HtmlTree ul = new HtmlTree(HtmlTag.UL); + ul.addStyle(HtmlStyle.blockList); + return ul; + } + + /** + * {@inheritDoc} + */ + public void addClassConstant(Content summariesTree, Content classConstantTree) { + if (configuration.allowTag(HtmlTag.SECTION)) { + summaryTree.addContent(classConstantTree); + } else { + summariesTree.addContent(classConstantTree); + } + } + + /** + * Get the table caption and header for the constant summary table + * + * @param typeElement the TypeElement to be documented + * @return constant members header content + */ + public Content getConstantMembersHeader(TypeElement typeElement) { + //generate links backward only to public classes. + Content classlink = (utils.isPublic(typeElement) || utils.isProtected(typeElement)) ? + getLink(new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.CONSTANT_SUMMARY, typeElement)) : + new StringContent(utils.getFullyQualifiedName(typeElement)); + + PackageElement enclosingPackage = utils.containingPackage(typeElement); + if (!enclosingPackage.isUnnamed()) { + Content cb = new ContentBuilder(); + cb.addContent(enclosingPackage.getQualifiedName().toString()); + cb.addContent("."); + cb.addContent(classlink); + return getClassName(cb); + } else { + return getClassName(classlink); + } + } + + /** + * Get the class name in the table caption and the table header. + * + * @param classStr the class name to print. + * @return the table caption and header + */ + protected Content getClassName(Content classStr) { + Content caption = getTableCaption(classStr); + Content table = (configuration.isOutputHtml5()) + ? HtmlTree.TABLE(HtmlStyle.constantsSummary, caption) + : HtmlTree.TABLE(HtmlStyle.constantsSummary, constantsTableSummary, caption); + table.addContent(getSummaryTableHeader(constantsTableHeader, "col")); + return table; + } + + /** + * {@inheritDoc} + */ + public void addConstantMembers(TypeElement typeElement, Collection fields, + Content classConstantTree) { + currentTypeElement = typeElement; + Content tbody = new HtmlTree(HtmlTag.TBODY); + boolean altColor = true; + for (VariableElement field : fields) { + HtmlTree tr = new HtmlTree(HtmlTag.TR); + tr.addStyle(altColor ? HtmlStyle.altColor : HtmlStyle.rowColor); + addConstantMember(field, tr); + tbody.addContent(tr); + altColor = !altColor; + } + Content table = getConstantMembersHeader(typeElement); + table.addContent(tbody); + Content li = HtmlTree.LI(HtmlStyle.blockList, table); + classConstantTree.addContent(li); + } + + /** + * Add the row for the constant summary table. + * + * @param member the field to be documented. + * @param trTree an htmltree object for the table row + */ + private void addConstantMember(VariableElement member, HtmlTree trTree) { + trTree.addContent(getTypeColumn(member)); + trTree.addContent(getNameColumn(member)); + trTree.addContent(getValue(member)); + } + + /** + * Get the type column for the constant summary table row. + * + * @param member the field to be documented. + * @return the type column of the constant table row + */ + private Content getTypeColumn(VariableElement member) { + Content anchor = getMarkerAnchor(currentTypeElement.getQualifiedName() + + "." + member.getSimpleName()); + Content tdType = HtmlTree.TD(HtmlStyle.colFirst, anchor); + Content code = new HtmlTree(HtmlTag.CODE); + for (Modifier mod : member.getModifiers()) { + Content modifier = new StringContent(mod.toString()); + code.addContent(modifier); + code.addContent(getSpace()); + } + Content type = getLink(new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.CONSTANT_SUMMARY, member.asType())); + code.addContent(type); + tdType.addContent(code); + return tdType; + } + + /** + * Get the name column for the constant summary table row. + * + * @param member the field to be documented. + * @return the name column of the constant table row + */ + private Content getNameColumn(VariableElement member) { + Content nameContent = getDocLink(LinkInfoImpl.Kind.CONSTANT_SUMMARY, + member, member.getSimpleName().toString(), false); + Content code = HtmlTree.CODE(nameContent); + return HtmlTree.TD(code); + } + + /** + * Get the value column for the constant summary table row. + * + * @param member the field to be documented. + * @return the value column of the constant table row + */ + private Content getValue(VariableElement member) { + String value = utils.constantValueExpresion(member); + Content valueContent = new StringContent(value); + Content code = HtmlTree.CODE(valueContent); + return HtmlTree.TD(HtmlStyle.colLast, code); + } + + /** + * {@inheritDoc} + */ + public void addConstantSummaries(Content contentTree, Content summariesTree) { + if (configuration.allowTag(HtmlTag.SECTION) && summaryTree != null) { + summariesTree.addContent(summaryTree); + } + if (configuration.allowTag(HtmlTag.MAIN)) { + mainTree.addContent(summariesTree); + contentTree.addContent(mainTree); + } else { + contentTree.addContent(summariesTree); + } + } + + /** + * {@inheritDoc} + */ + public void addFooter(Content contentTree) { + Content htmlTree = (configuration.allowTag(HtmlTag.FOOTER)) + ? HtmlTree.FOOTER() + : contentTree; + addNavLinks(false, htmlTree); + addBottom(htmlTree); + if (configuration.allowTag(HtmlTag.FOOTER)) { + contentTree.addContent(htmlTree); + } + } + + /** + * {@inheritDoc} + */ + public void printDocument(Content contentTree) throws IOException { + printHtmlDocument(null, true, contentTree); + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ConstructorWriterImpl.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ConstructorWriterImpl.java new file mode 100644 index 00000000000..c3f3a41e163 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ConstructorWriterImpl.java @@ -0,0 +1,354 @@ +/* + * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; +import java.util.*; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; + +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.ConstructorWriter; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.MemberSummaryWriter; +import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberMap; + + +/** + * Writes constructor documentation. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Robert Field + * @author Atul M Dambalkar + * @author Bhavesh Patel (Modified) + */ +public class ConstructorWriterImpl extends AbstractExecutableMemberWriter + implements ConstructorWriter, MemberSummaryWriter { + + private boolean foundNonPubConstructor = false; + + /** + * Construct a new ConstructorWriterImpl. + * + * @param writer The writer for the class that the constructors belong to. + * @param typeElement the class being documented. + */ + public ConstructorWriterImpl(SubWriterHolderWriter writer, TypeElement typeElement) { + super(writer, typeElement); + + VisibleMemberMap visibleMemberMap = new VisibleMemberMap( + typeElement, + VisibleMemberMap.Kind.CONSTRUCTORS, configuration); + SortedSet constructors = visibleMemberMap.getMembersFor(typeElement); + for (Element constructor : constructors) { + if (utils.isProtected(constructor) || utils.isPrivate(constructor)) { + setFoundNonPubConstructor(true); + } + } + } + + /** + * Construct a new ConstructorWriterImpl. + * + * @param writer The writer for the class that the constructors belong to. + */ + public ConstructorWriterImpl(SubWriterHolderWriter writer) { + super(writer); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getMemberSummaryHeader(TypeElement typeElement, + Content memberSummaryTree) { + memberSummaryTree.addContent(HtmlConstants.START_OF_CONSTRUCTOR_SUMMARY); + Content memberTree = writer.getMemberTreeHeader(); + writer.addSummaryHeader(this, typeElement, memberTree); + return memberTree; + } + + /** + * {@inheritDoc} + */ + public void addMemberTree(Content memberSummaryTree, Content memberTree) { + writer.addMemberTree(memberSummaryTree, memberTree); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getConstructorDetailsTreeHeader(TypeElement typeElement, + Content memberDetailsTree) { + memberDetailsTree.addContent(HtmlConstants.START_OF_CONSTRUCTOR_DETAILS); + Content constructorDetailsTree = writer.getMemberTreeHeader(); + constructorDetailsTree.addContent(writer.getMarkerAnchor( + SectionName.CONSTRUCTOR_DETAIL)); + Content heading = HtmlTree.HEADING(HtmlConstants.DETAILS_HEADING, + writer.constructorDetailsLabel); + constructorDetailsTree.addContent(heading); + return constructorDetailsTree; + } + + /** + * {@inheritDoc} + */ + @Override + public Content getConstructorDocTreeHeader(ExecutableElement constructor, + Content constructorDetailsTree) { + String erasureAnchor; + if ((erasureAnchor = getErasureAnchor(constructor)) != null) { + constructorDetailsTree.addContent(writer.getMarkerAnchor((erasureAnchor))); + } + constructorDetailsTree.addContent( + writer.getMarkerAnchor(writer.getAnchor(constructor))); + Content constructorDocTree = writer.getMemberTreeHeader(); + Content heading = new HtmlTree(HtmlConstants.MEMBER_HEADING); + heading.addContent(name(constructor)); + constructorDocTree.addContent(heading); + return constructorDocTree; + } + + /** + * {@inheritDoc} + */ + @Override + public Content getSignature(ExecutableElement constructor) { + Content pre = new HtmlTree(HtmlTag.PRE); + writer.addAnnotationInfo(constructor, pre); + int annotationLength = pre.charCount(); + addModifiers(constructor, pre); + if (configuration.linksource) { + Content constructorName = new StringContent(name(constructor)); + writer.addSrcLink(constructor, constructorName, pre); + } else { + addName(name(constructor), pre); + } + int indent = pre.charCount() - annotationLength; + addParameters(constructor, pre, indent); + addExceptions(constructor, pre, indent); + return pre; + } + + /** + * {@inheritDoc} + */ + @Override + public void setSummaryColumnStyle(HtmlTree tdTree) { + if (foundNonPubConstructor) + tdTree.addStyle(HtmlStyle.colLast); + else + tdTree.addStyle(HtmlStyle.colOne); + } + + /** + * {@inheritDoc} + */ + @Override + public void addDeprecated(ExecutableElement constructor, Content constructorDocTree) { + addDeprecatedInfo(constructor, constructorDocTree); + } + + /** + * {@inheritDoc} + */ + @Override + public void addComments(ExecutableElement constructor, Content constructorDocTree) { + addComment(constructor, constructorDocTree); + } + + /** + * {@inheritDoc} + */ + @Override + public void addTags(ExecutableElement constructor, Content constructorDocTree) { + writer.addTagsInfo(constructor, constructorDocTree); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getConstructorDetails(Content constructorDetailsTree) { + if (configuration.allowTag(HtmlTag.SECTION)) { + HtmlTree htmlTree = HtmlTree.SECTION(getMemberTree(constructorDetailsTree)); + return htmlTree; + } + return getMemberTree(constructorDetailsTree); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getConstructorDoc(Content constructorDocTree, + boolean isLastContent) { + return getMemberTree(constructorDocTree, isLastContent); + } + + /** + * Close the writer. + */ + @Override + public void close() throws IOException { + writer.close(); + } + + /** + * Let the writer know whether a non public constructor was found. + * + * @param foundNonPubConstructor true if we found a non public constructor. + */ + @Override + public void setFoundNonPubConstructor(boolean foundNonPubConstructor) { + this.foundNonPubConstructor = foundNonPubConstructor; + } + + /** + * {@inheritDoc} + */ + @Override + public void addSummaryLabel(Content memberTree) { + Content label = HtmlTree.HEADING(HtmlConstants.SUMMARY_HEADING, + writer.getResource("doclet.Constructor_Summary")); + memberTree.addContent(label); + } + + /** + * {@inheritDoc} + */ + @Override + public String getTableSummary() { + return configuration.getText("doclet.Member_Table_Summary", + configuration.getText("doclet.Constructor_Summary"), + configuration.getText("doclet.constructors")); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getCaption() { + return configuration.getResource("doclet.Constructors"); + } + + /** + * {@inheritDoc} + */ + @Override + public List getSummaryTableHeader(Element member) { + List header = new ArrayList<>(); + if (foundNonPubConstructor) { + header.add(configuration.getText("doclet.Modifier")); + } + header.add(configuration.getText("doclet.0_and_1", + configuration.getText("doclet.Constructor"), + configuration.getText("doclet.Description"))); + return header; + } + + /** + * {@inheritDoc} + */ + @Override + public void addSummaryAnchor(TypeElement typeElement, Content memberTree) { + memberTree.addContent(writer.getMarkerAnchor( + SectionName.CONSTRUCTOR_SUMMARY)); + } + + /** + * {@inheritDoc} + */ + @Override + public void addInheritedSummaryAnchor(TypeElement typeElement, Content inheritedTree) { + } + + /** + * {@inheritDoc} + */ + @Override + public void addInheritedSummaryLabel(TypeElement typeElement, Content inheritedTree) { + } + + /** + * {@inheritDoc} + */ + @Override + protected Content getNavSummaryLink(TypeElement typeElement, boolean link) { + if (link) { + return writer.getHyperLink(SectionName.CONSTRUCTOR_SUMMARY, + writer.getResource("doclet.navConstructor")); + } else { + return writer.getResource("doclet.navConstructor"); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void addNavDetailLink(boolean link, Content liNav) { + if (link) { + liNav.addContent(writer.getHyperLink( + SectionName.CONSTRUCTOR_DETAIL, + writer.getResource("doclet.navConstructor"))); + } else { + liNav.addContent(writer.getResource("doclet.navConstructor")); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void addSummaryType(Element member, Content tdSummaryType) { + if (foundNonPubConstructor) { + Content code = new HtmlTree(HtmlTag.CODE); + if (utils.isProtected(member)) { + code.addContent("protected "); + } else if (utils.isPrivate(member)) { + code.addContent("private "); + } else if (utils.isPublic(member)) { + code.addContent(writer.getSpace()); + } else { + code.addContent( + configuration.getText("doclet.Package_private")); + } + tdSummaryType.addContent(code); + } + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/DeprecatedListWriter.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/DeprecatedListWriter.java new file mode 100644 index 00000000000..6defcd7614d --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/DeprecatedListWriter.java @@ -0,0 +1,385 @@ +/* + * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; + +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.DeprecatedAPIListBuilder; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; +import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; + +import static jdk.javadoc.internal.doclets.toolkit.util.DeprecatedAPIListBuilder.*; + +/** + * Generate File to list all the deprecated classes and class members with the + * appropriate links. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @see java.util.List + * @author Atul M Dambalkar + * @author Bhavesh Patel (Modified) + */ +public class DeprecatedListWriter extends SubWriterHolderWriter { + + private String getAnchorName(DeprElementKind kind) { + switch (kind) { + case PACKAGE: + return "package"; + case INTERFACE: + return "interface"; + case CLASS: + return "class"; + case ENUM: + return "enum"; + case EXCEPTION: + return "exception"; + case ERROR: + return "error"; + case ANNOTATION_TYPE: + return "annotation.type"; + case FIELD: + return "field"; + case METHOD: + return "method"; + case CONSTRUCTOR: + return "constructor"; + case ENUM_CONSTANT: + return "enum.constant"; + case ANNOTATION_TYPE_MEMBER: + return "annotation.type.member"; + default: + throw new AssertionError("unknown kind: " + kind); + } + } + + private String getHeadingKey(DeprElementKind kind) { + switch (kind) { + case PACKAGE: + return "doclet.Deprecated_Packages"; + case INTERFACE: + return "doclet.Deprecated_Interfaces"; + case CLASS: + return "doclet.Deprecated_Classes"; + case ENUM: + return "doclet.Deprecated_Enums"; + case EXCEPTION: + return "doclet.Deprecated_Exceptions"; + case ERROR: + return "doclet.Deprecated_Errors"; + case ANNOTATION_TYPE: + return "doclet.Deprecated_Annotation_Types"; + case FIELD: + return "doclet.Deprecated_Fields"; + case METHOD: + return "doclet.Deprecated_Methods"; + case CONSTRUCTOR: + return "doclet.Deprecated_Constructors"; + case ENUM_CONSTANT: + return "doclet.Deprecated_Enum_Constants"; + case ANNOTATION_TYPE_MEMBER: + return "doclet.Deprecated_Annotation_Type_Members"; + default: + throw new AssertionError("unknown kind: " + kind); + } + } + + private String getSummaryKey(DeprElementKind kind) { + switch (kind) { + case PACKAGE: + return "doclet.deprecated_packages"; + case INTERFACE: + return "doclet.deprecated_interfaces"; + case CLASS: + return "doclet.deprecated_classes"; + case ENUM: + return "doclet.deprecated_enums"; + case EXCEPTION: + return "doclet.deprecated_exceptions"; + case ERROR: + return "doclet.deprecated_errors"; + case ANNOTATION_TYPE: + return "doclet.deprecated_annotation_types"; + case FIELD: + return "doclet.deprecated_fields"; + case METHOD: + return "doclet.deprecated_methods"; + case CONSTRUCTOR: + return "doclet.deprecated_constructors"; + case ENUM_CONSTANT: + return "doclet.deprecated_enum_constants"; + case ANNOTATION_TYPE_MEMBER: + return "doclet.deprecated_annotation_type_members"; + default: + throw new AssertionError("unknown kind: " + kind); + } + } + + private String getHeaderKey(DeprElementKind kind) { + switch (kind) { + case PACKAGE: + return "doclet.Package"; + case INTERFACE: + return "doclet.Interface"; + case CLASS: + return "doclet.Class"; + case ENUM: + return "doclet.Enum"; + case EXCEPTION: + return "doclet.Exceptions"; + case ERROR: + return "doclet.Errors"; + case ANNOTATION_TYPE: + return "doclet.AnnotationType"; + case FIELD: + return "doclet.Field"; + case METHOD: + return "doclet.Method"; + case CONSTRUCTOR: + return "doclet.Constructor"; + case ENUM_CONSTANT: + return "doclet.Enum_Constant"; + case ANNOTATION_TYPE_MEMBER: + return "doclet.Annotation_Type_Member"; + default: + throw new AssertionError("unknown kind: " + kind); + } + } + + private EnumMap writerMap; + + private ConfigurationImpl configuration; + + /** + * Constructor. + * + * @param filename the file to be generated. + */ + + public DeprecatedListWriter(ConfigurationImpl configuration, + DocPath filename) throws IOException { + super(configuration, filename); + this.configuration = configuration; + NestedClassWriterImpl classW = new NestedClassWriterImpl(this); + writerMap = new EnumMap<>(DeprElementKind.class); + for (DeprElementKind kind : DeprElementKind.values()) { + switch (kind) { + case PACKAGE: + case INTERFACE: + case CLASS: + case ENUM: + case EXCEPTION: + case ERROR: + case ANNOTATION_TYPE: + writerMap.put(kind, classW); + break; + case FIELD: + writerMap.put(kind, new FieldWriterImpl(this)); + break; + case METHOD: + writerMap.put(kind, new MethodWriterImpl(this)); + break; + case CONSTRUCTOR: + writerMap.put(kind, new ConstructorWriterImpl(this)); + break; + case ENUM_CONSTANT: + writerMap.put(kind, new EnumConstantWriterImpl(this)); + break; + case ANNOTATION_TYPE_MEMBER: + writerMap.put(kind, new AnnotationTypeOptionalMemberWriterImpl(this, null)); + break; + default: + throw new AssertionError("unknown kind: " + kind); + } + } + } + + /** + * Get list of all the deprecated classes and members in all the Packages + * specified on the Command Line. + * Then instantiate DeprecatedListWriter and generate File. + * + * @param configuration the current configuration of the doclet. + */ + public static void generate(ConfigurationImpl configuration) { + DocPath filename = DocPaths.DEPRECATED_LIST; + try { + DeprecatedListWriter depr = + new DeprecatedListWriter(configuration, filename); + depr.generateDeprecatedListFile( + new DeprecatedAPIListBuilder(configuration)); + depr.close(); + } catch (IOException exc) { + configuration.standardmessage.error( + "doclet.exception_encountered", + exc.toString(), filename); + throw new DocletAbortException(exc); + } + } + + /** + * Generate the deprecated API list. + * + * @param deprapi list of deprecated API built already. + */ + protected void generateDeprecatedListFile(DeprecatedAPIListBuilder deprapi) + throws IOException { + HtmlTree body = getHeader(); + HtmlTree htmlTree = (configuration.allowTag(HtmlTag.MAIN)) + ? HtmlTree.MAIN() + : body; + htmlTree.addContent(getContentsList(deprapi)); + String memberTableSummary; + HtmlTree div = new HtmlTree(HtmlTag.DIV); + div.addStyle(HtmlStyle.contentContainer); + for (DeprElementKind kind : DeprElementKind.values()) { + if (deprapi.hasDocumentation(kind)) { + addAnchor(deprapi, kind, div); + memberTableSummary = + configuration.getText("doclet.Member_Table_Summary", + configuration.getText(getHeadingKey(kind)), + configuration.getText(getSummaryKey(kind))); + List memberTableHeader = new ArrayList<>(); + memberTableHeader.add(configuration.getText("doclet.0_and_1", + configuration.getText(getHeaderKey(kind)), + configuration.getText("doclet.Description"))); + if (kind == DeprElementKind.PACKAGE) + addPackageDeprecatedAPI(deprapi.getSet(kind), + getHeadingKey(kind), memberTableSummary, memberTableHeader, div); + else + writerMap.get(kind).addDeprecatedAPI(deprapi.getSet(kind), + getHeadingKey(kind), memberTableSummary, memberTableHeader, div); + } + } + if (configuration.allowTag(HtmlTag.MAIN)) { + htmlTree.addContent(div); + body.addContent(htmlTree); + } else { + body.addContent(div); + } + htmlTree = (configuration.allowTag(HtmlTag.FOOTER)) + ? HtmlTree.FOOTER() + : body; + addNavLinks(false, htmlTree); + addBottom(htmlTree); + if (configuration.allowTag(HtmlTag.FOOTER)) { + body.addContent(htmlTree); + } + printHtmlDocument(null, true, body); + } + + /** + * Add the index link. + * + * @param builder the deprecated list builder + * @param type the type of list being documented + * @param contentTree the content tree to which the index link will be added + */ + private void addIndexLink(DeprecatedAPIListBuilder builder, + DeprElementKind kind, Content contentTree) { + if (builder.hasDocumentation(kind)) { + Content li = HtmlTree.LI(getHyperLink(getAnchorName(kind), + getResource(getHeadingKey(kind)))); + contentTree.addContent(li); + } + } + + /** + * Get the contents list. + * + * @param deprapi the deprecated list builder + * @return a content tree for the contents list + */ + public Content getContentsList(DeprecatedAPIListBuilder deprapi) { + Content headContent = getResource("doclet.Deprecated_API"); + Content heading = HtmlTree.HEADING(HtmlConstants.TITLE_HEADING, true, + HtmlStyle.title, headContent); + Content div = HtmlTree.DIV(HtmlStyle.header, heading); + Content headingContent = getResource("doclet.Contents"); + div.addContent(HtmlTree.HEADING(HtmlConstants.CONTENT_HEADING, true, + headingContent)); + Content ul = new HtmlTree(HtmlTag.UL); + for (DeprElementKind kind : DeprElementKind.values()) { + addIndexLink(deprapi, kind, ul); + } + div.addContent(ul); + return div; + } + + /** + * Add the anchor. + * + * @param builder the deprecated list builder + * @param type the type of list being documented + * @param htmlTree the content tree to which the anchor will be added + */ + private void addAnchor(DeprecatedAPIListBuilder builder, DeprElementKind kind, Content htmlTree) { + if (builder.hasDocumentation(kind)) { + htmlTree.addContent(getMarkerAnchor(getAnchorName(kind))); + } + } + + /** + * Get the header for the deprecated API Listing. + * + * @return a content tree for the header + */ + public HtmlTree getHeader() { + String title = configuration.getText("doclet.Window_Deprecated_List"); + HtmlTree bodyTree = getBody(true, getWindowTitle(title)); + HtmlTree htmlTree = (configuration.allowTag(HtmlTag.HEADER)) + ? HtmlTree.HEADER() + : bodyTree; + addTop(htmlTree); + addNavLinks(true, htmlTree); + if (configuration.allowTag(HtmlTag.HEADER)) { + bodyTree.addContent(htmlTree); + } + return bodyTree; + } + + /** + * Get the deprecated label. + * + * @return a content tree for the deprecated label + */ + protected Content getNavLinkDeprecated() { + Content li = HtmlTree.LI(HtmlStyle.navBarCell1Rev, deprecatedLabel); + return li; + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/EnumConstantWriterImpl.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/EnumConstantWriterImpl.java new file mode 100644 index 00000000000..fc8bf385bb6 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/EnumConstantWriterImpl.java @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; + +import java.util.Arrays; +import java.util.List; + +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; + +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.EnumConstantWriter; +import jdk.javadoc.internal.doclets.toolkit.MemberSummaryWriter; + +/** + * Writes enum constant documentation in HTML format. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Jamie Ho + * @author Bhavesh Patel (Modified) + */ +public class EnumConstantWriterImpl extends AbstractMemberWriter + implements EnumConstantWriter, MemberSummaryWriter { + + public EnumConstantWriterImpl(SubWriterHolderWriter writer, TypeElement typeElement) { + super(writer, typeElement); + } + + public EnumConstantWriterImpl(SubWriterHolderWriter writer) { + super(writer); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getMemberSummaryHeader(TypeElement typeElement, + Content memberSummaryTree) { + memberSummaryTree.addContent(HtmlConstants.START_OF_ENUM_CONSTANT_SUMMARY); + Content memberTree = writer.getMemberTreeHeader(); + writer.addSummaryHeader(this, typeElement, memberTree); + return memberTree; + } + + /** + * {@inheritDoc} + */ + public void addMemberTree(Content memberSummaryTree, Content memberTree) { + writer.addMemberTree(memberSummaryTree, memberTree); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getEnumConstantsDetailsTreeHeader(TypeElement typeElement, + Content memberDetailsTree) { + memberDetailsTree.addContent(HtmlConstants.START_OF_ENUM_CONSTANT_DETAILS); + Content enumConstantsDetailsTree = writer.getMemberTreeHeader(); + enumConstantsDetailsTree.addContent(writer.getMarkerAnchor( + SectionName.ENUM_CONSTANT_DETAIL)); + Content heading = HtmlTree.HEADING(HtmlConstants.DETAILS_HEADING, + writer.enumConstantsDetailsLabel); + enumConstantsDetailsTree.addContent(heading); + return enumConstantsDetailsTree; + } + + /** + * {@inheritDoc} + */ + @Override + public Content getEnumConstantsTreeHeader(VariableElement enumConstant, + Content enumConstantsDetailsTree) { + enumConstantsDetailsTree.addContent( + writer.getMarkerAnchor(name(enumConstant))); + Content enumConstantsTree = writer.getMemberTreeHeader(); + Content heading = new HtmlTree(HtmlConstants.MEMBER_HEADING); + heading.addContent(name(enumConstant)); + enumConstantsTree.addContent(heading); + return enumConstantsTree; + } + + /** + * {@inheritDoc} + */ + @Override + public Content getSignature(VariableElement enumConstant) { + Content pre = new HtmlTree(HtmlTag.PRE); + writer.addAnnotationInfo(enumConstant, pre); + addModifiers(enumConstant, pre); + Content enumConstantLink = writer.getLink(new LinkInfoImpl( + configuration, LinkInfoImpl.Kind.MEMBER, enumConstant.asType())); + pre.addContent(enumConstantLink); + pre.addContent(" "); + if (configuration.linksource) { + Content enumConstantName = new StringContent(name(enumConstant)); + writer.addSrcLink(enumConstant, enumConstantName, pre); + } else { + addName(name(enumConstant), pre); + } + return pre; + } + + /** + * {@inheritDoc} + */ + @Override + public void addDeprecated(VariableElement enumConstant, Content enumConstantsTree) { + addDeprecatedInfo(enumConstant, enumConstantsTree); + } + + /** + * {@inheritDoc} + */ + @Override + public void addComments(VariableElement enumConstant, Content enumConstantsTree) { + addComment(enumConstant, enumConstantsTree); + } + + /** + * {@inheritDoc} + */ + @Override + public void addTags(VariableElement enumConstant, Content enumConstantsTree) { + writer.addTagsInfo(enumConstant, enumConstantsTree); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getEnumConstantsDetails(Content enumConstantsDetailsTree) { + if (configuration.allowTag(HtmlTag.SECTION)) { + HtmlTree htmlTree = HtmlTree.SECTION(getMemberTree(enumConstantsDetailsTree)); + return htmlTree; + } + return getMemberTree(enumConstantsDetailsTree); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getEnumConstants(Content enumConstantsTree, + boolean isLastContent) { + return getMemberTree(enumConstantsTree, isLastContent); + } + + /** + * {@inheritDoc} + */ + @Override + public void close() throws IOException { + writer.close(); + } + + /** + * {@inheritDoc} + */ + @Override + public void addSummaryLabel(Content memberTree) { + Content label = HtmlTree.HEADING(HtmlConstants.SUMMARY_HEADING, + writer.getResource("doclet.Enum_Constant_Summary")); + memberTree.addContent(label); + } + + /** + * {@inheritDoc} + */ + @Override + public String getTableSummary() { + return configuration.getText("doclet.Member_Table_Summary", + configuration.getText("doclet.Enum_Constant_Summary"), + configuration.getText("doclet.enum_constants")); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getCaption() { + return configuration.getResource("doclet.Enum_Constants"); + } + + /** + * {@inheritDoc} + */ + @Override + public List getSummaryTableHeader(Element member) { + List header = Arrays.asList(configuration.getText("doclet.0_and_1", + configuration.getText("doclet.Enum_Constant"), + configuration.getText("doclet.Description"))); + return header; + } + + /** + * {@inheritDoc} + */ + @Override + public void addSummaryAnchor(TypeElement typeElement, Content memberTree) { + memberTree.addContent(writer.getMarkerAnchor( + SectionName.ENUM_CONSTANT_SUMMARY)); + } + + /** + * {@inheritDoc} + */ + @Override + public void addInheritedSummaryAnchor(TypeElement typeElement, Content inheritedTree) { + } + + /** + * {@inheritDoc} + */ + @Override + public void addInheritedSummaryLabel(TypeElement typeElement, Content inheritedTree) { + } + + /** + * {@inheritDoc} + */ + @Override + protected void addSummaryLink(LinkInfoImpl.Kind context, TypeElement typeElement, Element member, + Content tdSummary) { + Content memberLink = HtmlTree.SPAN(HtmlStyle.memberNameLink, + writer.getDocLink(context, member, name(member), false)); + Content code = HtmlTree.CODE(memberLink); + tdSummary.addContent(code); + } + + /** + * {@inheritDoc} + */ + @Override + public void setSummaryColumnStyle(HtmlTree tdTree) { + tdTree.addStyle(HtmlStyle.colOne); + } + + /** + * {@inheritDoc} + */ + @Override + protected void addInheritedSummaryLink(TypeElement typeElement, Element member, Content linksTree) { + } + + /** + * {@inheritDoc} + */ + @Override + protected void addSummaryType(Element member, Content tdSummaryType) { + //Not applicable. + } + + /** + * {@inheritDoc} + */ + @Override + protected Content getDeprecatedLink(Element member) { + String name = utils.getFullyQualifiedName(member) + "." + member.getSimpleName(); + return writer.getDocLink(LinkInfoImpl.Kind.MEMBER, member, name); + } + + /** + * {@inheritDoc} + */ + @Override + protected Content getNavSummaryLink(TypeElement typeElement, boolean link) { + if (link) { + if (typeElement == null) { + return writer.getHyperLink(SectionName.ENUM_CONSTANT_SUMMARY, + writer.getResource("doclet.navEnum")); + } else { + return writer.getHyperLink( + SectionName.ENUM_CONSTANTS_INHERITANCE, + configuration.getClassName(typeElement), writer.getResource("doclet.navEnum")); + } + } else { + return writer.getResource("doclet.navEnum"); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void addNavDetailLink(boolean link, Content liNav) { + if (link) { + liNav.addContent(writer.getHyperLink( + SectionName.ENUM_CONSTANT_DETAIL, + writer.getResource("doclet.navEnum"))); + } else { + liNav.addContent(writer.getResource("doclet.navEnum")); + } + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/FieldWriterImpl.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/FieldWriterImpl.java new file mode 100644 index 00000000000..b5e6c03b5f1 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/FieldWriterImpl.java @@ -0,0 +1,339 @@ +/* + * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; +import java.util.Arrays; +import java.util.List; + +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; + +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.FieldWriter; +import jdk.javadoc.internal.doclets.toolkit.MemberSummaryWriter; + +/** + * Writes field documentation in HTML format. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Robert Field + * @author Atul M Dambalkar + * @author Jamie Ho (rewrite) + * @author Bhavesh Patel (Modified) + */ +public class FieldWriterImpl extends AbstractMemberWriter + implements FieldWriter, MemberSummaryWriter { + + public FieldWriterImpl(SubWriterHolderWriter writer, TypeElement typeElement) { + super(writer, typeElement); + } + + public FieldWriterImpl(SubWriterHolderWriter writer) { + super(writer); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getMemberSummaryHeader(TypeElement typeElement, + Content memberSummaryTree) { + memberSummaryTree.addContent(HtmlConstants.START_OF_FIELD_SUMMARY); + Content memberTree = writer.getMemberTreeHeader(); + writer.addSummaryHeader(this, typeElement, memberTree); + return memberTree; + } + + /** + * {@inheritDoc} + */ + public void addMemberTree(Content memberSummaryTree, Content memberTree) { + writer.addMemberTree(memberSummaryTree, memberTree); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getFieldDetailsTreeHeader(TypeElement typeElement, Content memberDetailsTree) { + memberDetailsTree.addContent(HtmlConstants.START_OF_FIELD_DETAILS); + Content fieldDetailsTree = writer.getMemberTreeHeader(); + fieldDetailsTree.addContent(writer.getMarkerAnchor( + SectionName.FIELD_DETAIL)); + Content heading = HtmlTree.HEADING(HtmlConstants.DETAILS_HEADING, + writer.fieldDetailsLabel); + fieldDetailsTree.addContent(heading); + return fieldDetailsTree; + } + + /** + * {@inheritDoc} + */ + @Override + public Content getFieldDocTreeHeader(VariableElement field, Content fieldDetailsTree) { + fieldDetailsTree.addContent(writer.getMarkerAnchor(name(field))); + Content fieldTree = writer.getMemberTreeHeader(); + Content heading = new HtmlTree(HtmlConstants.MEMBER_HEADING); + heading.addContent(name(field)); + fieldTree.addContent(heading); + return fieldTree; + } + + /** + * {@inheritDoc} + */ + @Override + public Content getSignature(VariableElement field) { + Content pre = new HtmlTree(HtmlTag.PRE); + writer.addAnnotationInfo(field, pre); + addModifiers(field, pre); + Content fieldlink = writer.getLink(new LinkInfoImpl( + configuration, LinkInfoImpl.Kind.MEMBER, field.asType())); + pre.addContent(fieldlink); + pre.addContent(" "); + if (configuration.linksource) { + Content fieldName = new StringContent(name(field)); + writer.addSrcLink(field, fieldName, pre); + } else { + addName(name(field), pre); + } + return pre; + } + + /** + * {@inheritDoc} + */ + @Override + public void addDeprecated(VariableElement field, Content fieldTree) { + addDeprecatedInfo(field, fieldTree); + } + + /** + * {@inheritDoc} + */ + @Override + public void addComments(VariableElement field, Content fieldTree) { + if (!utils.getBody(field).isEmpty()) { + writer.addInlineComment(field, fieldTree); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void addTags(VariableElement field, Content fieldTree) { + writer.addTagsInfo(field, fieldTree); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getFieldDetails(Content fieldDetailsTree) { + if (configuration.allowTag(HtmlTag.SECTION)) { + HtmlTree htmlTree = HtmlTree.SECTION(getMemberTree(fieldDetailsTree)); + return htmlTree; + } + return getMemberTree(fieldDetailsTree); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getFieldDoc(Content fieldTree, + boolean isLastContent) { + return getMemberTree(fieldTree, isLastContent); + } + + /** + * Close the writer. + */ + @Override + public void close() throws IOException { + writer.close(); + } + + /** + * {@inheritDoc} + */ + @Override + public void addSummaryLabel(Content memberTree) { + Content label = HtmlTree.HEADING(HtmlConstants.SUMMARY_HEADING, + writer.getResource("doclet.Field_Summary")); + memberTree.addContent(label); + } + + /** + * {@inheritDoc} + */ + @Override + public String getTableSummary() { + return configuration.getText("doclet.Member_Table_Summary", + configuration.getText("doclet.Field_Summary"), + configuration.getText("doclet.fields")); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getCaption() { + return configuration.getResource("doclet.Fields"); + } + + /** + * {@inheritDoc} + */ + @Override + public List getSummaryTableHeader(Element member) { + List header = Arrays.asList(writer.getModifierTypeHeader(), + configuration.getText("doclet.0_and_1", + configuration.getText("doclet.Field"), + configuration.getText("doclet.Description"))); + return header; + } + + /** + * {@inheritDoc} + */ + @Override + public void addSummaryAnchor(TypeElement typeElement, Content memberTree) { + memberTree.addContent(writer.getMarkerAnchor( + SectionName.FIELD_SUMMARY)); + } + + /** + * {@inheritDoc} + */ + @Override + public void addInheritedSummaryAnchor(TypeElement typeElement, Content inheritedTree) { + inheritedTree.addContent(writer.getMarkerAnchor( + SectionName.FIELDS_INHERITANCE, configuration.getClassName(typeElement))); + } + + /** + * {@inheritDoc} + */ + @Override + public void addInheritedSummaryLabel(TypeElement typeElement, Content inheritedTree) { + Content classLink = writer.getPreQualifiedClassLink( + LinkInfoImpl.Kind.MEMBER, typeElement, false); + Content label = new StringContent(utils.isClass(typeElement) + ? configuration.getText("doclet.Fields_Inherited_From_Class") + : configuration.getText("doclet.Fields_Inherited_From_Interface")); + Content labelHeading = HtmlTree.HEADING(HtmlConstants.INHERITED_SUMMARY_HEADING, + label); + labelHeading.addContent(writer.getSpace()); + labelHeading.addContent(classLink); + inheritedTree.addContent(labelHeading); + } + + /** + * {@inheritDoc} + */ + @Override + protected void addSummaryLink(LinkInfoImpl.Kind context, TypeElement typeElement, Element member, + Content tdSummary) { + Content memberLink = HtmlTree.SPAN(HtmlStyle.memberNameLink, + writer.getDocLink(context, typeElement , member, name(member), false)); + Content code = HtmlTree.CODE(memberLink); + tdSummary.addContent(code); + } + + /** + * {@inheritDoc} + */ + @Override + protected void addInheritedSummaryLink(TypeElement typeElement, Element member, Content linksTree) { + linksTree.addContent( + writer.getDocLink(LinkInfoImpl.Kind.MEMBER, typeElement, member, + name(member), false)); + } + + /** + * {@inheritDoc} + */ + @Override + protected void addSummaryType(Element member, Content tdSummaryType) { + addModifierAndType(member, member.asType(), tdSummaryType); + } + + /** + * {@inheritDoc} + */ + @Override + protected Content getDeprecatedLink(Element member) { + String name = utils.getFullyQualifiedName(member) + "." + member.getSimpleName(); + return writer.getDocLink(LinkInfoImpl.Kind.MEMBER, member, name); + } + + /** + * {@inheritDoc} + */ + @Override + protected Content getNavSummaryLink(TypeElement typeElement, boolean link) { + if (link) { + if (typeElement == null) { + return writer.getHyperLink( + SectionName.FIELD_SUMMARY, + writer.getResource("doclet.navField")); + } else { + return writer.getHyperLink( + SectionName.FIELDS_INHERITANCE, + configuration.getClassName(typeElement), writer.getResource("doclet.navField")); + } + } else { + return writer.getResource("doclet.navField"); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void addNavDetailLink(boolean link, Content liNav) { + if (link) { + liNav.addContent(writer.getHyperLink( + SectionName.FIELD_DETAIL, + writer.getResource("doclet.navField"))); + } else { + liNav.addContent(writer.getResource("doclet.navField")); + } + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/FrameOutputWriter.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/FrameOutputWriter.java new file mode 100644 index 00000000000..69be1be292e --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/FrameOutputWriter.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; + +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; +import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; + + +/** + * Generate the documentation in the Html "frame" format in the browser. The + * generated documentation will have two or three frames depending upon the + * number of packages on the command line. In general there will be three frames + * in the output, a left-hand top frame will have a list of all packages with + * links to target left-hand bottom frame. The left-hand bottom frame will have + * the particular package contents or the all-classes list, where as the single + * right-hand frame will have overview or package summary or class file. Also + * take care of browsers which do not support Html frames. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Atul M Dambalkar + */ +public class FrameOutputWriter extends HtmlDocletWriter { + + /** + * Number of packages specified on the command line. + */ + int noOfPackages; + + /** + * Constructor to construct FrameOutputWriter object. + * + * @param configuration for this run + * @param filename File to be generated. + * @throws java.io.IOException + */ + public FrameOutputWriter(ConfigurationImpl configuration, DocPath filename) throws IOException { + super(configuration, filename); + noOfPackages = configuration.packages.size(); + } + + /** + * Construct FrameOutputWriter object and then use it to generate the Html + * file which will have the description of all the frames in the + * documentation. The name of the generated file is "index.html" which is + * the default first file for Html documents. + * @throws DocletAbortException + */ + public static void generate(ConfigurationImpl configuration) { + FrameOutputWriter framegen; + DocPath filename = DocPath.empty; + try { + filename = DocPaths.INDEX; + framegen = new FrameOutputWriter(configuration, filename); + framegen.generateFrameFile(); + framegen.close(); + } catch (IOException exc) { + configuration.standardmessage.error( + "doclet.exception_encountered", + exc.toString(), filename); + throw new DocletAbortException(exc); + } + } + + /** + * Generate the constants in the "index.html" file. Print the frame details + * as well as warning if browser is not supporting the Html frames. + */ + protected void generateFrameFile() throws IOException { + Content frame = getFrameDetails(); + HtmlTree body = new HtmlTree(HtmlTag.BODY); + if (configuration.allowTag(HtmlTag.MAIN)) { + HtmlTree main = HtmlTree.MAIN(frame); + body.addContent(main); + } else { + body.addContent(frame); + } + if (configuration.windowtitle.length() > 0) { + printFramesDocument(configuration.windowtitle, configuration, + body); + } else { + printFramesDocument(configuration.getText("doclet.Generated_Docs_Untitled"), + configuration, body); + } + } + + /** + * Get the frame sizes and their contents. + * + * @return a content tree for the frame details + */ + protected Content getFrameDetails() { + HtmlTree leftContainerDiv = new HtmlTree(HtmlTag.DIV); + HtmlTree rightContainerDiv = new HtmlTree(HtmlTag.DIV); + leftContainerDiv.addStyle(HtmlStyle.leftContainer); + rightContainerDiv.addStyle(HtmlStyle.rightContainer); + if (noOfPackages <= 1) { + addAllClassesFrameTag(leftContainerDiv); + } else if (noOfPackages > 1) { + addAllPackagesFrameTag(leftContainerDiv); + addAllClassesFrameTag(leftContainerDiv); + } + addClassFrameTag(rightContainerDiv); + HtmlTree mainContainer = HtmlTree.DIV(HtmlStyle.mainContainer, leftContainerDiv); + mainContainer.addContent(rightContainerDiv); + return mainContainer; + } + + /** + * Add the IFRAME tag for the frame that lists all packages. + * + * @param contentTree the content tree to which the information will be added + */ + private void addAllPackagesFrameTag(Content contentTree) { + HtmlTree frame = HtmlTree.IFRAME(DocPaths.OVERVIEW_FRAME.getPath(), + "packageListFrame", configuration.getText("doclet.All_Packages")); + HtmlTree leftTop = HtmlTree.DIV(HtmlStyle.leftTop, frame); + contentTree.addContent(leftTop); + } + + /** + * Add the IFRAME tag for the frame that lists all classes. + * + * @param contentTree the content tree to which the information will be added + */ + private void addAllClassesFrameTag(Content contentTree) { + HtmlTree frame = HtmlTree.IFRAME(DocPaths.ALLCLASSES_FRAME.getPath(), + "packageFrame", configuration.getText("doclet.All_classes_and_interfaces")); + HtmlTree leftBottom = HtmlTree.DIV(HtmlStyle.leftBottom, frame); + contentTree.addContent(leftBottom); + } + + /** + * Add the IFRAME tag for the frame that describes the class in detail. + * + * @param contentTree the content tree to which the information will be added + */ + private void addClassFrameTag(Content contentTree) { + HtmlTree frame = HtmlTree.IFRAME(configuration.topFile.getPath(), "classFrame", + configuration.getText("doclet.Package_class_and_interface_descriptions")); + frame.addStyle(HtmlStyle.rightIframe); + contentTree.addContent(frame); + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HelpWriter.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HelpWriter.java new file mode 100644 index 00000000000..784cabd3bc9 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HelpWriter.java @@ -0,0 +1,449 @@ +/* + * Copyright (c) 1998, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; + +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; +import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; + + +/** + * Generate the Help File for the generated API documentation. The help file + * contents are helpful for browsing the generated documentation. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Atul M Dambalkar + */ +public class HelpWriter extends HtmlDocletWriter { + + HtmlTree mainTree = HtmlTree.MAIN(); + + /** + * Constructor to construct HelpWriter object. + * @param filename File to be generated. + */ + public HelpWriter(ConfigurationImpl configuration, + DocPath filename) throws IOException { + super(configuration, filename); + } + + /** + * Construct the HelpWriter object and then use it to generate the help + * file. The name of the generated file is "help-doc.html". The help file + * will get generated if and only if "-helpfile" and "-nohelp" is not used + * on the command line. + * @throws DocletAbortException + */ + public static void generate(ConfigurationImpl configuration) { + HelpWriter helpgen; + DocPath filename = DocPath.empty; + try { + filename = DocPaths.HELP_DOC; + helpgen = new HelpWriter(configuration, filename); + helpgen.generateHelpFile(); + helpgen.close(); + } catch (IOException exc) { + configuration.standardmessage.error( + "doclet.exception_encountered", + exc.toString(), filename); + throw new DocletAbortException(exc); + } + } + + /** + * Generate the help file contents. + */ + protected void generateHelpFile() throws IOException { + String title = configuration.getText("doclet.Window_Help_title"); + HtmlTree body = getBody(true, getWindowTitle(title)); + HtmlTree htmlTree = (configuration.allowTag(HtmlTag.HEADER)) + ? HtmlTree.HEADER() + : body; + addTop(htmlTree); + addNavLinks(true, htmlTree); + if (configuration.allowTag(HtmlTag.HEADER)) { + body.addContent(htmlTree); + } + addHelpFileContents(body); + if (configuration.allowTag(HtmlTag.FOOTER)) { + htmlTree = HtmlTree.FOOTER(); + } + addNavLinks(false, htmlTree); + addBottom(htmlTree); + if (configuration.allowTag(HtmlTag.FOOTER)) { + body.addContent(htmlTree); + } + printHtmlDocument(null, true, body); + } + + /** + * Add the help file contents from the resource file to the content tree. While adding the + * help file contents it also keeps track of user options. If "-notree" + * is used, then the "overview-tree.html" will not get added and hence + * help information also will not get added. + * + * @param contentTree the content tree to which the help file contents will be added + */ + protected void addHelpFileContents(Content contentTree) { + Content heading = HtmlTree.HEADING(HtmlConstants.TITLE_HEADING, false, HtmlStyle.title, + getResource("doclet.Help_line_1")); + Content div = HtmlTree.DIV(HtmlStyle.header, heading); + Content line2 = HtmlTree.DIV(HtmlStyle.subTitle, + getResource("doclet.Help_line_2")); + div.addContent(line2); + if (configuration.allowTag(HtmlTag.MAIN)) { + mainTree.addContent(div); + } else { + contentTree.addContent(div); + } + HtmlTree htmlTree; + HtmlTree ul = new HtmlTree(HtmlTag.UL); + ul.addStyle(HtmlStyle.blockList); + if (configuration.createoverview) { + Content overviewHeading = HtmlTree.HEADING(HtmlConstants.CONTENT_HEADING, + getResource("doclet.Overview")); + htmlTree = (configuration.allowTag(HtmlTag.SECTION)) + ? HtmlTree.SECTION(overviewHeading) + : HtmlTree.LI(HtmlStyle.blockList, overviewHeading); + Content line3 = getResource("doclet.Help_line_3", + getHyperLink(DocPaths.OVERVIEW_SUMMARY, + configuration.getText("doclet.Overview"))); + Content overviewPara = HtmlTree.P(line3); + htmlTree.addContent(overviewPara); + if (configuration.allowTag(HtmlTag.SECTION)) { + ul.addContent(HtmlTree.LI(HtmlStyle.blockList, htmlTree)); + } else { + ul.addContent(htmlTree); + } + } + Content packageHead = HtmlTree.HEADING(HtmlConstants.CONTENT_HEADING, + getResource("doclet.Package")); + htmlTree = (configuration.allowTag(HtmlTag.SECTION)) + ? HtmlTree.SECTION(packageHead) + : HtmlTree.LI(HtmlStyle.blockList, packageHead); + Content line4 = getResource("doclet.Help_line_4"); + Content packagePara = HtmlTree.P(line4); + htmlTree.addContent(packagePara); + HtmlTree ulPackage = new HtmlTree(HtmlTag.UL); + ulPackage.addContent(HtmlTree.LI( + getResource("doclet.Interfaces_Italic"))); + ulPackage.addContent(HtmlTree.LI( + getResource("doclet.Classes"))); + ulPackage.addContent(HtmlTree.LI( + getResource("doclet.Enums"))); + ulPackage.addContent(HtmlTree.LI( + getResource("doclet.Exceptions"))); + ulPackage.addContent(HtmlTree.LI( + getResource("doclet.Errors"))); + ulPackage.addContent(HtmlTree.LI( + getResource("doclet.AnnotationTypes"))); + htmlTree.addContent(ulPackage); + if (configuration.allowTag(HtmlTag.SECTION)) { + ul.addContent(HtmlTree.LI(HtmlStyle.blockList, htmlTree)); + } else { + ul.addContent(htmlTree); + } + Content classHead = HtmlTree.HEADING(HtmlConstants.CONTENT_HEADING, + getResource("doclet.Help_line_5")); + htmlTree = (configuration.allowTag(HtmlTag.SECTION)) + ? HtmlTree.SECTION(classHead) + : HtmlTree.LI(HtmlStyle.blockList, classHead); + Content line6 = getResource("doclet.Help_line_6"); + Content classPara = HtmlTree.P(line6); + htmlTree.addContent(classPara); + HtmlTree ul1 = new HtmlTree(HtmlTag.UL); + ul1.addContent(HtmlTree.LI( + getResource("doclet.Help_line_7"))); + ul1.addContent(HtmlTree.LI( + getResource("doclet.Help_line_8"))); + ul1.addContent(HtmlTree.LI( + getResource("doclet.Help_line_9"))); + ul1.addContent(HtmlTree.LI( + getResource("doclet.Help_line_10"))); + ul1.addContent(HtmlTree.LI( + getResource("doclet.Help_line_11"))); + ul1.addContent(HtmlTree.LI( + getResource("doclet.Help_line_12"))); + htmlTree.addContent(ul1); + HtmlTree ul2 = new HtmlTree(HtmlTag.UL); + ul2.addContent(HtmlTree.LI( + getResource("doclet.Nested_Class_Summary"))); + ul2.addContent(HtmlTree.LI( + getResource("doclet.Field_Summary"))); + ul2.addContent(HtmlTree.LI( + getResource("doclet.Constructor_Summary"))); + ul2.addContent(HtmlTree.LI( + getResource("doclet.Method_Summary"))); + htmlTree.addContent(ul2); + HtmlTree ul3 = new HtmlTree(HtmlTag.UL); + ul3.addContent(HtmlTree.LI( + getResource("doclet.Field_Detail"))); + ul3.addContent(HtmlTree.LI( + getResource("doclet.Constructor_Detail"))); + ul3.addContent(HtmlTree.LI( + getResource("doclet.Method_Detail"))); + htmlTree.addContent(ul3); + Content line13 = getResource("doclet.Help_line_13"); + Content para = HtmlTree.P(line13); + htmlTree.addContent(para); + if (configuration.allowTag(HtmlTag.SECTION)) { + ul.addContent(HtmlTree.LI(HtmlStyle.blockList, htmlTree)); + } else { + ul.addContent(htmlTree); + } + //Annotation Types + Content aHead = HtmlTree.HEADING(HtmlConstants.CONTENT_HEADING, + getResource("doclet.AnnotationType")); + htmlTree = (configuration.allowTag(HtmlTag.SECTION)) + ? HtmlTree.SECTION(aHead) + : HtmlTree.LI(HtmlStyle.blockList, aHead); + Content aline1 = getResource("doclet.Help_annotation_type_line_1"); + Content aPara = HtmlTree.P(aline1); + htmlTree.addContent(aPara); + HtmlTree aul = new HtmlTree(HtmlTag.UL); + aul.addContent(HtmlTree.LI( + getResource("doclet.Help_annotation_type_line_2"))); + aul.addContent(HtmlTree.LI( + getResource("doclet.Help_annotation_type_line_3"))); + aul.addContent(HtmlTree.LI( + getResource("doclet.Annotation_Type_Required_Member_Summary"))); + aul.addContent(HtmlTree.LI( + getResource("doclet.Annotation_Type_Optional_Member_Summary"))); + aul.addContent(HtmlTree.LI( + getResource("doclet.Annotation_Type_Member_Detail"))); + htmlTree.addContent(aul); + if (configuration.allowTag(HtmlTag.SECTION)) { + ul.addContent(HtmlTree.LI(HtmlStyle.blockList, htmlTree)); + } else { + ul.addContent(htmlTree); + } + //Enums + Content enumHead = HtmlTree.HEADING(HtmlConstants.CONTENT_HEADING, + getResource("doclet.Enum")); + htmlTree = (configuration.allowTag(HtmlTag.SECTION)) + ? HtmlTree.SECTION(enumHead) + : HtmlTree.LI(HtmlStyle.blockList, enumHead); + Content eline1 = getResource("doclet.Help_enum_line_1"); + Content enumPara = HtmlTree.P(eline1); + htmlTree.addContent(enumPara); + HtmlTree eul = new HtmlTree(HtmlTag.UL); + eul.addContent(HtmlTree.LI( + getResource("doclet.Help_enum_line_2"))); + eul.addContent(HtmlTree.LI( + getResource("doclet.Help_enum_line_3"))); + eul.addContent(HtmlTree.LI( + getResource("doclet.Enum_Constant_Summary"))); + eul.addContent(HtmlTree.LI( + getResource("doclet.Enum_Constant_Detail"))); + htmlTree.addContent(eul); + if (configuration.allowTag(HtmlTag.SECTION)) { + ul.addContent(HtmlTree.LI(HtmlStyle.blockList, htmlTree)); + } else { + ul.addContent(htmlTree); + } + if (configuration.classuse) { + Content useHead = HtmlTree.HEADING(HtmlConstants.CONTENT_HEADING, + getResource("doclet.Help_line_14")); + htmlTree = (configuration.allowTag(HtmlTag.SECTION)) + ? HtmlTree.SECTION(useHead) + : HtmlTree.LI(HtmlStyle.blockList, useHead); + Content line15 = getResource("doclet.Help_line_15"); + Content usePara = HtmlTree.P(line15); + htmlTree.addContent(usePara); + if (configuration.allowTag(HtmlTag.SECTION)) { + ul.addContent(HtmlTree.LI(HtmlStyle.blockList, htmlTree)); + } else { + ul.addContent(htmlTree); + } + } + if (configuration.createtree) { + Content treeHead = HtmlTree.HEADING(HtmlConstants.CONTENT_HEADING, + getResource("doclet.Help_line_16")); + htmlTree = (configuration.allowTag(HtmlTag.SECTION)) + ? HtmlTree.SECTION(treeHead) + : HtmlTree.LI(HtmlStyle.blockList, treeHead); + Content line17 = getResource("doclet.Help_line_17_with_tree_link", + getHyperLink(DocPaths.OVERVIEW_TREE, + configuration.getText("doclet.Class_Hierarchy")), + HtmlTree.CODE(new StringContent("java.lang.Object"))); + Content treePara = HtmlTree.P(line17); + htmlTree.addContent(treePara); + HtmlTree tul = new HtmlTree(HtmlTag.UL); + tul.addContent(HtmlTree.LI( + getResource("doclet.Help_line_18"))); + tul.addContent(HtmlTree.LI( + getResource("doclet.Help_line_19"))); + htmlTree.addContent(tul); + if (configuration.allowTag(HtmlTag.SECTION)) { + ul.addContent(HtmlTree.LI(HtmlStyle.blockList, htmlTree)); + } else { + ul.addContent(htmlTree); + } + } + if (!(configuration.nodeprecatedlist || + configuration.nodeprecated)) { + Content dHead = HtmlTree.HEADING(HtmlConstants.CONTENT_HEADING, + getResource("doclet.Deprecated_API")); + htmlTree = (configuration.allowTag(HtmlTag.SECTION)) + ? HtmlTree.SECTION(dHead) + : HtmlTree.LI(HtmlStyle.blockList, dHead); + Content line20 = getResource("doclet.Help_line_20_with_deprecated_api_link", + getHyperLink(DocPaths.DEPRECATED_LIST, + configuration.getText("doclet.Deprecated_API"))); + Content dPara = HtmlTree.P(line20); + htmlTree.addContent(dPara); + if (configuration.allowTag(HtmlTag.SECTION)) { + ul.addContent(HtmlTree.LI(HtmlStyle.blockList, htmlTree)); + } else { + ul.addContent(htmlTree); + } + } + if (configuration.createindex) { + Content indexlink; + if (configuration.splitindex) { + indexlink = getHyperLink(DocPaths.INDEX_FILES.resolve(DocPaths.indexN(1)), + configuration.getText("doclet.Index")); + } else { + indexlink = getHyperLink(DocPaths.INDEX_ALL, + configuration.getText("doclet.Index")); + } + Content indexHead = HtmlTree.HEADING(HtmlConstants.CONTENT_HEADING, + getResource("doclet.Help_line_21")); + htmlTree = (configuration.allowTag(HtmlTag.SECTION)) + ? HtmlTree.SECTION(indexHead) + : HtmlTree.LI(HtmlStyle.blockList, indexHead); + Content line22 = getResource("doclet.Help_line_22", indexlink); + Content indexPara = HtmlTree.P(line22); + htmlTree.addContent(indexPara); + if (configuration.allowTag(HtmlTag.SECTION)) { + ul.addContent(HtmlTree.LI(HtmlStyle.blockList, htmlTree)); + } else { + ul.addContent(htmlTree); + } + } + Content prevHead = HtmlTree.HEADING(HtmlConstants.CONTENT_HEADING, + getResource("doclet.Help_line_23")); + htmlTree = (configuration.allowTag(HtmlTag.SECTION)) + ? HtmlTree.SECTION(prevHead) + : HtmlTree.LI(HtmlStyle.blockList, prevHead); + Content line24 = getResource("doclet.Help_line_24"); + Content prevPara = HtmlTree.P(line24); + htmlTree.addContent(prevPara); + if (configuration.allowTag(HtmlTag.SECTION)) { + ul.addContent(HtmlTree.LI(HtmlStyle.blockList, htmlTree)); + } else { + ul.addContent(htmlTree); + } + Content frameHead = HtmlTree.HEADING(HtmlConstants.CONTENT_HEADING, + getResource("doclet.Help_line_25")); + htmlTree = (configuration.allowTag(HtmlTag.SECTION)) + ? HtmlTree.SECTION(frameHead) + : HtmlTree.LI(HtmlStyle.blockList, frameHead); + Content line26 = getResource("doclet.Help_line_26"); + Content framePara = HtmlTree.P(line26); + htmlTree.addContent(framePara); + if (configuration.allowTag(HtmlTag.SECTION)) { + ul.addContent(HtmlTree.LI(HtmlStyle.blockList, htmlTree)); + } else { + ul.addContent(htmlTree); + } + Content allclassesHead = HtmlTree.HEADING(HtmlConstants.CONTENT_HEADING, + getResource("doclet.All_Classes")); + htmlTree = (configuration.allowTag(HtmlTag.SECTION)) + ? HtmlTree.SECTION(allclassesHead) + : HtmlTree.LI(HtmlStyle.blockList, allclassesHead); + Content line27 = getResource("doclet.Help_line_27", + getHyperLink(DocPaths.ALLCLASSES_NOFRAME, + configuration.getText("doclet.All_Classes"))); + Content allclassesPara = HtmlTree.P(line27); + htmlTree.addContent(allclassesPara); + if (configuration.allowTag(HtmlTag.SECTION)) { + ul.addContent(HtmlTree.LI(HtmlStyle.blockList, htmlTree)); + } else { + ul.addContent(htmlTree); + } + Content sHead = HtmlTree.HEADING(HtmlConstants.CONTENT_HEADING, + getResource("doclet.Serialized_Form")); + htmlTree = (configuration.allowTag(HtmlTag.SECTION)) + ? HtmlTree.SECTION(sHead) + : HtmlTree.LI(HtmlStyle.blockList, sHead); + Content line28 = getResource("doclet.Help_line_28"); + Content serialPara = HtmlTree.P(line28); + htmlTree.addContent(serialPara); + if (configuration.allowTag(HtmlTag.SECTION)) { + ul.addContent(HtmlTree.LI(HtmlStyle.blockList, htmlTree)); + } else { + ul.addContent(htmlTree); + } + Content constHead = HtmlTree.HEADING(HtmlConstants.CONTENT_HEADING, + getResource("doclet.Constants_Summary")); + htmlTree = (configuration.allowTag(HtmlTag.SECTION)) + ? HtmlTree.SECTION(constHead) + : HtmlTree.LI(HtmlStyle.blockList, constHead); + Content line29 = getResource("doclet.Help_line_29", + getHyperLink(DocPaths.CONSTANT_VALUES, + configuration.getText("doclet.Constants_Summary"))); + Content constPara = HtmlTree.P(line29); + htmlTree.addContent(constPara); + if (configuration.allowTag(HtmlTag.SECTION)) { + ul.addContent(HtmlTree.LI(HtmlStyle.blockList, htmlTree)); + } else { + ul.addContent(htmlTree); + } + Content divContent = HtmlTree.DIV(HtmlStyle.contentContainer, ul); + Content line30 = HtmlTree.SPAN(HtmlStyle.emphasizedPhrase, getResource("doclet.Help_line_30")); + divContent.addContent(line30); + if (configuration.allowTag(HtmlTag.MAIN)) { + mainTree.addContent(divContent); + contentTree.addContent(mainTree); + } else { + contentTree.addContent(divContent); + } + } + + /** + * Get the help label. + * + * @return a content tree for the help label + */ + @Override + protected Content getNavLinkHelp() { + Content li = HtmlTree.LI(HtmlStyle.navBarCell1Rev, helpLabel); + return li; + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDoclet.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDoclet.java new file mode 100644 index 00000000000..b0f8ad9b418 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDoclet.java @@ -0,0 +1,323 @@ +/* + * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; +import java.util.*; + +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; + +import jdk.javadoc.doclet.Doclet.Option; +import jdk.javadoc.doclet.DocletEnvironment; +import jdk.javadoc.doclet.Reporter; +import jdk.javadoc.internal.doclets.toolkit.AbstractDoclet; +import jdk.javadoc.internal.doclets.toolkit.Configuration; +import jdk.javadoc.internal.doclets.toolkit.builders.AbstractBuilder; +import jdk.javadoc.internal.doclets.toolkit.util.ClassTree; +import jdk.javadoc.internal.doclets.toolkit.util.DocFile; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; +import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; +import jdk.javadoc.internal.doclets.toolkit.util.IndexBuilder; + +/** + * The class with "start" method, calls individual Writers. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Atul M Dambalkar + * @author Robert Field + * @author Jamie Ho + * + */ +public class HtmlDoclet extends AbstractDoclet { + + public HtmlDoclet() { + configuration = new ConfigurationImpl(); + } + + /** + * The global configuration information for this run. + */ + public final ConfigurationImpl configuration; + + + private static final DocPath DOCLET_RESOURCES = DocPath + .create("/jdk/javadoc/internal/doclets/formats/html/resources"); + + public void init(Locale locale, Reporter reporter) { + configuration.reporter = reporter; + configuration.locale = locale; + } + + /** + * The "start" method as required by Javadoc. + * + * @param root the root of the documentation tree. + * @see jdk.doclet.DocletEnvironment + * @return true if the doclet ran without encountering any errors. + */ + public boolean run(DocletEnvironment root) { + return startDoclet(root); + } + + /** + * Create the configuration instance. + * Override this method to use a different + * configuration. + */ + public Configuration configuration() { + return configuration; + } + + /** + * Start the generation of files. Call generate methods in the individual + * writers, which will in turn genrate the documentation files. Call the + * TreeWriter generation first to ensure the Class Hierarchy is built + * first and then can be used in the later generation. + * + * For new format. + * + * @see jdk.doclet.RootDoc + */ + protected void generateOtherFiles(DocletEnvironment root, ClassTree classtree) + throws Exception { + super.generateOtherFiles(root, classtree); + if (configuration.linksource) { + SourceToHTMLConverter.convertRoot(configuration, + root, DocPaths.SOURCE_OUTPUT); + } + + if (configuration.topFile.isEmpty()) { + configuration.standardmessage. + error("doclet.No_Non_Deprecated_Classes_To_Document"); + return; + } + boolean nodeprecated = configuration.nodeprecated; + performCopy(configuration.helpfile); + performCopy(configuration.stylesheetfile); + // do early to reduce memory footprint + if (configuration.classuse) { + ClassUseWriter.generate(configuration, classtree); + } + IndexBuilder indexbuilder = new IndexBuilder(configuration, nodeprecated); + + if (configuration.createtree) { + TreeWriter.generate(configuration, classtree); + } + if (configuration.createindex) { + configuration.buildSearchTagIndex(); + if (configuration.splitindex) { + SplitIndexWriter.generate(configuration, indexbuilder); + } else { + SingleIndexWriter.generate(configuration, indexbuilder); + } + } + + if (!(configuration.nodeprecatedlist || nodeprecated)) { + DeprecatedListWriter.generate(configuration); + } + + AllClassesFrameWriter.generate(configuration, + new IndexBuilder(configuration, nodeprecated, true)); + + FrameOutputWriter.generate(configuration); + + if (configuration.createoverview) { + PackageIndexWriter.generate(configuration); + } + if (configuration.helpfile.length() == 0 && + !configuration.nohelp) { + HelpWriter.generate(configuration); + } + // If a stylesheet file is not specified, copy the default stylesheet + // and replace newline with platform-specific newline. + DocFile f; + if (configuration.stylesheetfile.length() == 0) { + f = DocFile.createFileForOutput(configuration, DocPaths.STYLESHEET); + f.copyResource(DocPaths.RESOURCES.resolve(DocPaths.STYLESHEET), false, true); + } + f = DocFile.createFileForOutput(configuration, DocPaths.JAVASCRIPT); + f.copyResource(DocPaths.RESOURCES.resolve(DocPaths.JAVASCRIPT), true, true); + if (configuration.createindex) { + f = DocFile.createFileForOutput(configuration, DocPaths.SEARCH_JS); + f.copyResource(DOCLET_RESOURCES.resolve(DocPaths.SEARCH_JS), true, true); + + f = DocFile.createFileForOutput(configuration, DocPaths.RESOURCES.resolve(DocPaths.GLASS_IMG)); + f.copyResource(DOCLET_RESOURCES.resolve(DocPaths.GLASS_IMG), true, false); + + f = DocFile.createFileForOutput(configuration, DocPaths.RESOURCES.resolve(DocPaths.X_IMG)); + f.copyResource(DOCLET_RESOURCES.resolve(DocPaths.X_IMG), true, false); + copyJqueryFiles(); + } + } + + protected void copyJqueryFiles() { + List files = Arrays.asList( + "jquery-1.10.2.js", + "jquery-ui.js", + "jquery-ui.css", + "jquery-ui.min.js", + "jquery-ui.min.css", + "jquery-ui.structure.min.css", + "jquery-ui.structure.css", + "external/jquery/jquery.js", + "jszip/dist/jszip.js", + "jszip/dist/jszip.min.js", + "jszip-utils/dist/jszip-utils.js", + "jszip-utils/dist/jszip-utils.min.js", + "jszip-utils/dist/jszip-utils-ie.js", + "jszip-utils/dist/jszip-utils-ie.min.js", + "images/ui-bg_flat_0_aaaaaa_40x100.png", + "images/ui-icons_454545_256x240.png", + "images/ui-bg_glass_95_fef1ec_1x400.png", + "images/ui-bg_glass_75_dadada_1x400.png", + "images/ui-bg_highlight-soft_75_cccccc_1x100.png", + "images/ui-icons_888888_256x240.png", + "images/ui-icons_2e83ff_256x240.png", + "images/ui-bg_glass_65_ffffff_1x400.png", + "images/ui-icons_cd0a0a_256x240.png", + "images/ui-bg_glass_55_fbf9ee_1x400.png", + "images/ui-icons_222222_256x240.png", + "images/ui-bg_glass_75_e6e6e6_1x400.png", + "images/ui-bg_flat_75_ffffff_40x100.png"); + DocFile f; + for (String file : files) { + DocPath filePath = DocPaths.JQUERY_FILES.resolve(file); + f = DocFile.createFileForOutput(configuration, filePath); + f.copyResource(DOCLET_RESOURCES.resolve(filePath), true, false); + } + } + + /** + * {@inheritDoc} + */ + protected void generateClassFiles(SortedSet arr, ClassTree classtree) { + List list = new ArrayList<>(arr); + ListIterator iterator = list.listIterator(); + TypeElement klass = null; + while (iterator.hasNext()) { + TypeElement prev = iterator.hasPrevious() ? klass : null; + klass = iterator.next(); + TypeElement next = iterator.nextIndex() == list.size() + ? null : list.get(iterator.nextIndex()); + if (!(configuration.isGeneratedDoc(klass) && utils.isIncluded(klass))) { + continue; + } + try { + if (utils.isAnnotationType(klass)) { + AbstractBuilder annotationTypeBuilder = + configuration.getBuilderFactory() + .getAnnotationTypeBuilder(klass, + prev == null ? null : prev.asType(), + next == null ? null : next.asType()); + annotationTypeBuilder.build(); + } else { + AbstractBuilder classBuilder = + configuration.getBuilderFactory().getClassBuilder(klass, + prev, next, classtree); + classBuilder.build(); + } + } catch (IOException e) { + throw new DocletAbortException(e); + } catch (DocletAbortException de) { + throw de; + } catch (Exception e) { + e.printStackTrace(); + throw new DocletAbortException(e); + } + } + } + + + /** + * {@inheritDoc} + */ + protected void generatePackageFiles(ClassTree classtree) throws Exception { + Set packages = configuration.packages; + if (packages.size() > 1) { + PackageIndexFrameWriter.generate(configuration); + } + List pList = new ArrayList<>(packages); + PackageElement prev = null; + for (int i = 0 ; i < pList.size() ; i++) { + // if -nodeprecated option is set and the package is marked as + // deprecated, do not generate the package-summary.html, package-frame.html + // and package-tree.html pages for that package. + PackageElement pkg = pList.get(i); + if (!(configuration.nodeprecated && utils.isDeprecated(pkg))) { + PackageFrameWriter.generate(configuration, pkg); + int nexti = i + 1; + PackageElement next = null; + if (nexti < pList.size()) { + next = pList.get(nexti); + // If the next package is unnamed package, skip 2 ahead if possible + if (next.isUnnamed() && ++nexti < pList.size()) { + next = pList.get(nexti); + } + } + AbstractBuilder packageSummaryBuilder = + configuration.getBuilderFactory().getPackageSummaryBuilder( + pkg, prev, next); + packageSummaryBuilder.build(); + if (configuration.createtree) { + PackageTreeWriter.generate(configuration, pkg, prev, next, + configuration.nodeprecated); + } + prev = pkg; + } + } + } + + public Set

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Atul M Dambalkar + * @author Robert Field + * @author Bhavesh Patel (Modified) + */ +public class HtmlDocletWriter extends HtmlDocWriter { + + /** + * Relative path from the file getting generated to the destination + * directory. For example, if the file getting generated is + * "java/lang/Object.html", then the path to the root is "../..". + * This string can be empty if the file getting generated is in + * the destination directory. + */ + public final DocPath pathToRoot; + + /** + * Platform-independent path from the current or the + * destination directory to the file getting generated. + * Used when creating the file. + */ + public final DocPath path; + + /** + * Name of the file getting generated. If the file getting generated is + * "java/lang/Object.html", then the filename is "Object.html". + */ + public final DocPath filename; + + /** + * The global configuration information for this run. + */ + public final ConfigurationImpl configuration; + + protected final Utils utils; + + /** + * To check whether annotation heading is printed or not. + */ + protected boolean printedAnnotationHeading = false; + + /** + * To check whether annotation field heading is printed or not. + */ + protected boolean printedAnnotationFieldHeading = false; + + /** + * To check whether the repeated annotations is documented or not. + */ + private boolean isAnnotationDocumented = false; + + /** + * To check whether the container annotations is documented or not. + */ + private boolean isContainerDocumented = false; + + HtmlTree fixedNavDiv = new HtmlTree(HtmlTag.DIV); + + /** + * Constructor to construct the HtmlStandardWriter object. + * + * @param path File to be generated. + */ + public HtmlDocletWriter(ConfigurationImpl configuration, DocPath path) + throws IOException { + super(configuration, path); + this.configuration = configuration; + this.utils = configuration.utils; + this.path = path; + this.pathToRoot = path.parent().invert(); + this.filename = path.basename(); + } + + /** + * Replace {@docRoot} tag used in options that accept HTML text, such + * as -header, -footer, -top and -bottom, and when converting a relative + * HREF where commentTagsToString inserts a {@docRoot} where one was + * missing. (Also see DocRootTaglet for {@docRoot} tags in doc + * comments.) + *

+ * Replace {@docRoot} tag in htmlstr with the relative path to the + * destination directory from the directory where the file is being + * written, looping to handle all such tags in htmlstr. + *

+ * For example, for "-d docs" and -header containing {@docRoot}, when + * the HTML page for source file p/C1.java is being generated, the + * {@docRoot} tag would be inserted into the header as "../", + * the relative path from docs/p/ to docs/ (the document root). + *

+ * Note: This doc comment was written with '&#064;' representing '@' + * to prevent the inline tag from being interpreted. + */ + public String replaceDocRootDir(String htmlstr) { + // Return if no inline tags exist + int index = htmlstr.indexOf("{@"); + if (index < 0) { + return htmlstr; + } + Matcher docrootMatcher = docrootPattern.matcher(htmlstr); + if (!docrootMatcher.find()) { + return htmlstr; + } + StringBuilder buf = new StringBuilder(); + int prevEnd = 0; + do { + int match = docrootMatcher.start(); + // append htmlstr up to start of next {@docroot} + buf.append(htmlstr.substring(prevEnd, match)); + prevEnd = docrootMatcher.end(); + if (configuration.docrootparent.length() > 0 && htmlstr.startsWith("/..", prevEnd)) { + // Insert the absolute link if {@docRoot} is followed by "/..". + buf.append(configuration.docrootparent); + prevEnd += 3; + } else { + // Insert relative path where {@docRoot} was located + buf.append(pathToRoot.isEmpty() ? "." : pathToRoot.getPath()); + } + // Append slash if next character is not a slash + if (prevEnd < htmlstr.length() && htmlstr.charAt(prevEnd) != '/') { + buf.append('/'); + } + } while (docrootMatcher.find()); + buf.append(htmlstr.substring(prevEnd)); + return buf.toString(); + } + //where: + // Note: {@docRoot} is not case sensitive when passed in w/command line option: + private static final Pattern docrootPattern = + Pattern.compile(Pattern.quote("{@docroot}"), Pattern.CASE_INSENSITIVE); + + /** + * Get the script to show or hide the All classes link. + * + * @param id id of the element to show or hide + * @return a content tree for the script + */ + public Content getAllClassesLinkScript(String id) { + HtmlTree script = HtmlTree.SCRIPT(); + String scriptCode = "" + DocletConstants.NL; + Content scriptContent = new RawHtml(scriptCode); + script.addContent(scriptContent); + Content div = HtmlTree.DIV(script); + Content div_noscript = HtmlTree.DIV(getResource("doclet.No_Script_Message")); + Content noScript = HtmlTree.NOSCRIPT(div_noscript); + div.addContent(noScript); + return div; + } + + /** + * Add method information. + * + * @param method the method to be documented + * @param dl the content tree to which the method information will be added + */ + private void addMethodInfo(ExecutableElement method, Content dl) { + TypeElement enclosing = utils.getEnclosingTypeElement(method); + List intfacs = enclosing.getInterfaces(); + ExecutableElement overriddenMethod = utils.overriddenMethod(method); + // Check whether there is any implementation or overridden info to be + // printed. If no overridden or implementation info needs to be + // printed, do not print this section. + if ((!intfacs.isEmpty() + && new ImplementedMethods(method, this.configuration).build().isEmpty() == false) + || overriddenMethod != null) { + MethodWriterImpl.addImplementsInfo(this, method, dl); + if (overriddenMethod != null) { + MethodWriterImpl.addOverridden(this, + utils.overriddenType(method), + overriddenMethod, + dl); + } + } + } + + /** + * Adds the tags information. + * + * @param e the Element for which the tags will be generated + * @param htmltree the documentation tree to which the tags will be added + */ + protected void addTagsInfo(Element e, Content htmltree) { + if (configuration.nocomment) { + return; + } + Content dl = new HtmlTree(HtmlTag.DL); + if (utils.isExecutableElement(e) && !utils.isConstructor(e)) { + addMethodInfo((ExecutableElement)e, dl); + } + Content output = new ContentBuilder(); + TagletWriter.genTagOutput(configuration.tagletManager, e, + configuration.tagletManager.getCustomTaglets(e), + getTagletWriterInstance(false), output); + dl.addContent(output); + htmltree.addContent(dl); + } + + /** + * Check whether there are any tags for Serialization Overview + * section to be printed. + * + * @param field the VariableElement object to check for tags. + * @return true if there are tags to be printed else return false. + */ + protected boolean hasSerializationOverviewTags(VariableElement field) { + Content output = new ContentBuilder(); + TagletWriter.genTagOutput(configuration.tagletManager, field, + configuration.tagletManager.getCustomTaglets(field), + getTagletWriterInstance(false), output); + return !output.isEmpty(); + } + + /** + * Returns a TagletWriter that knows how to write HTML. + * + * @return a TagletWriter that knows how to write HTML. + */ + public TagletWriter getTagletWriterInstance(boolean isFirstSentence) { + return new TagletWriterImpl(this, isFirstSentence); + } + + /** + * Get Package link, with target frame. + * + * @param pkg The link will be to the "package-summary.html" page for this package + * @param target name of the target frame + * @param label tag for the link + * @return a content for the target package link + */ + public Content getTargetPackageLink(PackageElement pkg, String target, + Content label) { + return getHyperLink(pathString(pkg, DocPaths.PACKAGE_SUMMARY), label, "", target); + } + + + public void addClassesSummary(SortedSet classes, String label, + String tableSummary, List tableHeader, Content summaryContentTree) { + if (!classes.isEmpty()) { + Content caption = getTableCaption(new RawHtml(label)); + Content table = (configuration.isOutputHtml5()) + ? HtmlTree.TABLE(HtmlStyle.typeSummary, caption) + : HtmlTree.TABLE(HtmlStyle.typeSummary, tableSummary, caption); + table.addContent(getSummaryTableHeader(tableHeader, "col")); + Content tbody = new HtmlTree(HtmlTag.TBODY); + boolean altColor = true; + for (TypeElement te : classes) { + if (!utils.isCoreClass(te) || + !configuration.isGeneratedDoc(te)) { + continue; + } + Content classContent = getLink(new LinkInfoImpl( + configuration, LinkInfoImpl.Kind.PACKAGE, te)); + Content tdClass = HtmlTree.TD(HtmlStyle.colFirst, classContent); + HtmlTree tr = HtmlTree.TR(tdClass); + tr.addStyle(altColor ? HtmlStyle.altColor : HtmlStyle.rowColor); + altColor = !altColor; + HtmlTree tdClassDescription = new HtmlTree(HtmlTag.TD); + tdClassDescription.addStyle(HtmlStyle.colLast); + if (utils.isDeprecated(te)) { + tdClassDescription.addContent(deprecatedLabel); + List tags = utils.getDeprecatedTrees(te); + if (!tags.isEmpty()) { + addSummaryDeprecatedComment(te, tags.get(0), tdClassDescription); + } + } else { + addSummaryComment(te, tdClassDescription); + } + tr.addContent(tdClassDescription); + tbody.addContent(tr); + } + table.addContent(tbody); + summaryContentTree.addContent(table); + } + } + + /** + * Generates the HTML document tree and prints it out. + * + * @param metakeywords Array of String keywords for META tag. Each element + * of the array is assigned to a separate META tag. + * Pass in null for no array + * @param includeScript true if printing windowtitle script + * false for files that appear in the left-hand frames + * @param body the body htmltree to be included in the document + */ + public void printHtmlDocument(List metakeywords, boolean includeScript, + Content body) throws IOException { + Content htmlDocType = configuration.isOutputHtml5() + ? DocType.HTML5 + : DocType.TRANSITIONAL; + Content htmlComment = new Comment(configuration.getText("doclet.New_Page")); + Content head = new HtmlTree(HtmlTag.HEAD); + head.addContent(getGeneratedBy(!configuration.notimestamp)); + head.addContent(getTitle()); + Content meta = HtmlTree.META("Content-Type", CONTENT_TYPE, + (configuration.charset.length() > 0) ? + configuration.charset : HtmlConstants.HTML_DEFAULT_CHARSET); + head.addContent(meta); + if (!configuration.notimestamp) { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + meta = HtmlTree.META(configuration.isOutputHtml5() + ? "dc.created" + : "date", dateFormat.format(new Date())); + head.addContent(meta); + } + if (metakeywords != null) { + for (String metakeyword : metakeywords) { + meta = HtmlTree.META("keywords", metakeyword); + head.addContent(meta); + } + } + addStyleSheetProperties(head); + addScriptProperties(head); + Content htmlTree = HtmlTree.HTML(configuration.getLocale().getLanguage(), + head, body); + Content htmlDocument = new HtmlDocument(htmlDocType, + htmlComment, htmlTree); + write(htmlDocument); + } + + /** + * Get the window title. + * + * @param title the title string to construct the complete window title + * @return the window title string + */ + public String getWindowTitle(String title) { + if (configuration.windowtitle.length() > 0) { + title += " (" + configuration.windowtitle + ")"; + } + return title; + } + + /** + * Get user specified header and the footer. + * + * @param header if true print the user provided header else print the + * user provided footer. + */ + public Content getUserHeaderFooter(boolean header) { + String content; + if (header) { + content = replaceDocRootDir(configuration.header); + } else { + if (configuration.footer.length() != 0) { + content = replaceDocRootDir(configuration.footer); + } else { + content = replaceDocRootDir(configuration.header); + } + } + Content rawContent = new RawHtml(content); + return rawContent; + } + + /** + * Adds the user specified top. + * + * @param htmlTree the content tree to which user specified top will be added + */ + public void addTop(Content htmlTree) { + Content top = new RawHtml(replaceDocRootDir(configuration.top)); + fixedNavDiv.addContent(top); + } + + /** + * Adds the user specified bottom. + * + * @param htmlTree the content tree to which user specified bottom will be added + */ + public void addBottom(Content htmlTree) { + Content bottom = new RawHtml(replaceDocRootDir(configuration.bottom)); + Content small = HtmlTree.SMALL(bottom); + Content p = HtmlTree.P(HtmlStyle.legalCopy, small); + htmlTree.addContent(p); + } + + /** + * Adds the navigation bar for the Html page at the top and and the bottom. + * + * @param header If true print navigation bar at the top of the page else + * @param htmlTree the HtmlTree to which the nav links will be added + */ + protected void addNavLinks(boolean header, Content htmlTree) { + if (!configuration.nonavbar) { + Content tree = (configuration.allowTag(HtmlTag.NAV)) + ? HtmlTree.NAV() + : htmlTree; + String allClassesId = "allclasses_"; + HtmlTree navDiv = new HtmlTree(HtmlTag.DIV); + fixedNavDiv.addStyle(HtmlStyle.fixedNav); + Content skipNavLinks = configuration.getResource("doclet.Skip_navigation_links"); + if (header) { + fixedNavDiv.addContent(HtmlConstants.START_OF_TOP_NAVBAR); + navDiv.addStyle(HtmlStyle.topNav); + allClassesId += "navbar_top"; + Content a = getMarkerAnchor(SectionName.NAVBAR_TOP); + //WCAG - Hyperlinks should contain text or an image with alt text - for AT tools + navDiv.addContent(a); + Content skipLinkContent = HtmlTree.DIV(HtmlStyle.skipNav, getHyperLink( + getDocLink(SectionName.SKIP_NAVBAR_TOP), skipNavLinks, + skipNavLinks.toString(), "")); + navDiv.addContent(skipLinkContent); + } else { + tree.addContent(HtmlConstants.START_OF_BOTTOM_NAVBAR); + navDiv.addStyle(HtmlStyle.bottomNav); + allClassesId += "navbar_bottom"; + Content a = getMarkerAnchor(SectionName.NAVBAR_BOTTOM); + navDiv.addContent(a); + Content skipLinkContent = HtmlTree.DIV(HtmlStyle.skipNav, getHyperLink( + getDocLink(SectionName.SKIP_NAVBAR_BOTTOM), skipNavLinks, + skipNavLinks.toString(), "")); + navDiv.addContent(skipLinkContent); + } + if (header) { + navDiv.addContent(getMarkerAnchor(SectionName.NAVBAR_TOP_FIRSTROW)); + } else { + navDiv.addContent(getMarkerAnchor(SectionName.NAVBAR_BOTTOM_FIRSTROW)); + } + HtmlTree navList = new HtmlTree(HtmlTag.UL); + navList.addStyle(HtmlStyle.navList); + navList.addAttr(HtmlAttr.TITLE, + configuration.getText("doclet.Navigation")); + if (configuration.createoverview) { + navList.addContent(getNavLinkContents()); + } + if (configuration.packages.size() == 1) { + navList.addContent(getNavLinkPackage(configuration.packages.first())); + } else if (!configuration.packages.isEmpty()) { + navList.addContent(getNavLinkPackage()); + } + navList.addContent(getNavLinkClass()); + if(configuration.classuse) { + navList.addContent(getNavLinkClassUse()); + } + if(configuration.createtree) { + navList.addContent(getNavLinkTree()); + } + if(!(configuration.nodeprecated || + configuration.nodeprecatedlist)) { + navList.addContent(getNavLinkDeprecated()); + } + if(configuration.createindex) { + navList.addContent(getNavLinkIndex()); + } + if (!configuration.nohelp) { + navList.addContent(getNavLinkHelp()); + } + navDiv.addContent(navList); + Content aboutDiv = HtmlTree.DIV(HtmlStyle.aboutLanguage, getUserHeaderFooter(header)); + navDiv.addContent(aboutDiv); + if (header) { + fixedNavDiv.addContent(navDiv); + } else { + tree.addContent(navDiv); + } + Content ulNav = HtmlTree.UL(HtmlStyle.navList, getNavLinkPrevious()); + ulNav.addContent(getNavLinkNext()); + Content subDiv = HtmlTree.DIV(HtmlStyle.subNav, ulNav); + Content ulFrames = HtmlTree.UL(HtmlStyle.navList, getNavShowLists()); + ulFrames.addContent(getNavHideLists(filename)); + subDiv.addContent(ulFrames); + HtmlTree ulAllClasses = HtmlTree.UL(HtmlStyle.navList, getNavLinkClassIndex()); + ulAllClasses.addAttr(HtmlAttr.ID, allClassesId); + subDiv.addContent(ulAllClasses); + if (header && configuration.createindex) { + HtmlTree inputText = HtmlTree.INPUT("text", "search"); + HtmlTree inputReset = HtmlTree.INPUT("reset", "reset"); + Content searchTxt = configuration.getResource("doclet.search"); + searchTxt.addContent(getSpace()); + HtmlTree liInput = HtmlTree.LI(HtmlTree.SPAN(searchTxt)); + liInput.addContent(inputText); + liInput.addContent(inputReset); + HtmlTree ulSearch = HtmlTree.UL(HtmlStyle.navListSearch, liInput); + subDiv.addContent(ulSearch); + } + subDiv.addContent(getAllClassesLinkScript(allClassesId)); + addSummaryDetailLinks(subDiv); + if (header) { + subDiv.addContent(getMarkerAnchor(SectionName.SKIP_NAVBAR_TOP)); + fixedNavDiv.addContent(subDiv); + fixedNavDiv.addContent(HtmlConstants.END_OF_TOP_NAVBAR); + tree.addContent(fixedNavDiv); + } else { + subDiv.addContent(getMarkerAnchor(SectionName.SKIP_NAVBAR_BOTTOM)); + tree.addContent(subDiv); + tree.addContent(HtmlConstants.END_OF_BOTTOM_NAVBAR); + } + if (configuration.allowTag(HtmlTag.NAV)) { + htmlTree.addContent(tree); + } + } + } + + /** + * Get the word "NEXT" to indicate that no link is available. Override + * this method to customize next link. + * + * @return a content tree for the link + */ + protected Content getNavLinkNext() { + return getNavLinkNext(null); + } + + /** + * Get the word "PREV" to indicate that no link is available. Override + * this method to customize prev link. + * + * @return a content tree for the link + */ + protected Content getNavLinkPrevious() { + return getNavLinkPrevious(null); + } + + /** + * Do nothing. This is the default method. + */ + protected void addSummaryDetailLinks(Content navDiv) { + } + + /** + * Get link to the "overview-summary.html" page. + * + * @return a content tree for the link + */ + protected Content getNavLinkContents() { + Content linkContent = getHyperLink(pathToRoot.resolve(DocPaths.OVERVIEW_SUMMARY), + overviewLabel, "", ""); + Content li = HtmlTree.LI(linkContent); + return li; + } + + /** + * Get link to the "package-summary.html" page for the package passed. + * + * @param pkg Package to which link will be generated + * @return a content tree for the link + */ + protected Content getNavLinkPackage(PackageElement pkg) { + Content linkContent = getPackageLink(pkg, packageLabel); + Content li = HtmlTree.LI(linkContent); + return li; + } + + /** + * Get the word "Package" , to indicate that link is not available here. + * + * @return a content tree for the link + */ + protected Content getNavLinkPackage() { + Content li = HtmlTree.LI(packageLabel); + return li; + } + + /** + * Get the word "Use", to indicate that link is not available. + * + * @return a content tree for the link + */ + protected Content getNavLinkClassUse() { + Content li = HtmlTree.LI(useLabel); + return li; + } + + /** + * Get link for previous file. + * + * @param prev File name for the prev link + * @return a content tree for the link + */ + public Content getNavLinkPrevious(DocPath prev) { + Content li; + if (prev != null) { + li = HtmlTree.LI(getHyperLink(prev, prevLabel, "", "")); + } + else + li = HtmlTree.LI(prevLabel); + return li; + } + + /** + * Get link for next file. If next is null, just print the label + * without linking it anywhere. + * + * @param next File name for the next link + * @return a content tree for the link + */ + public Content getNavLinkNext(DocPath next) { + Content li; + if (next != null) { + li = HtmlTree.LI(getHyperLink(next, nextLabel, "", "")); + } + else + li = HtmlTree.LI(nextLabel); + return li; + } + + /** + * Get "FRAMES" link, to switch to the frame version of the output. + * + * @param link File to be linked, "index.html" + * @return a content tree for the link + */ + protected Content getNavShowLists(DocPath link) { + DocLink dl = new DocLink(link, path.getPath(), null); + Content framesContent = getHyperLink(dl, framesLabel, "", "_top"); + Content li = HtmlTree.LI(framesContent); + return li; + } + + /** + * Get "FRAMES" link, to switch to the frame version of the output. + * + * @return a content tree for the link + */ + protected Content getNavShowLists() { + return getNavShowLists(pathToRoot.resolve(DocPaths.INDEX)); + } + + /** + * Get "NO FRAMES" link, to switch to the non-frame version of the output. + * + * @param link File to be linked + * @return a content tree for the link + */ + protected Content getNavHideLists(DocPath link) { + Content noFramesContent = getHyperLink(link, noframesLabel, "", "_top"); + Content li = HtmlTree.LI(noFramesContent); + return li; + } + + /** + * Get "Tree" link in the navigation bar. If there is only one package + * specified on the command line, then the "Tree" link will be to the + * only "package-tree.html" file otherwise it will be to the + * "overview-tree.html" file. + * + * @return a content tree for the link + */ + protected Content getNavLinkTree() { + List packages = new ArrayList<>(utils.getSpecifiedPackages()); + DocPath docPath = packages.size() == 1 && utils.getSpecifiedClasses().isEmpty() + ? pathString(packages.get(0), DocPaths.PACKAGE_TREE) + : pathToRoot.resolve(DocPaths.OVERVIEW_TREE); + return HtmlTree.LI(getHyperLink(docPath, treeLabel, "", "")); + } + + /** + * Get the overview tree link for the main tree. + * + * @param label the label for the link + * @return a content tree for the link + */ + protected Content getNavLinkMainTree(String label) { + Content mainTreeContent = getHyperLink(pathToRoot.resolve(DocPaths.OVERVIEW_TREE), + new StringContent(label)); + Content li = HtmlTree.LI(mainTreeContent); + return li; + } + + /** + * Get the word "Class", to indicate that class link is not available. + * + * @return a content tree for the link + */ + protected Content getNavLinkClass() { + Content li = HtmlTree.LI(classLabel); + return li; + } + + /** + * Get "Deprecated" API link in the navigation bar. + * + * @return a content tree for the link + */ + protected Content getNavLinkDeprecated() { + Content linkContent = getHyperLink(pathToRoot.resolve(DocPaths.DEPRECATED_LIST), + deprecatedLabel, "", ""); + Content li = HtmlTree.LI(linkContent); + return li; + } + + /** + * Get link for generated index. If the user has used "-splitindex" + * command line option, then link to file "index-files/index-1.html" is + * generated otherwise link to file "index-all.html" is generated. + * + * @return a content tree for the link + */ + protected Content getNavLinkClassIndex() { + Content allClassesContent = getHyperLink(pathToRoot.resolve( + DocPaths.ALLCLASSES_NOFRAME), + allclassesLabel, "", ""); + Content li = HtmlTree.LI(allClassesContent); + return li; + } + + /** + * Get link for generated class index. + * + * @return a content tree for the link + */ + protected Content getNavLinkIndex() { + Content linkContent = getHyperLink(pathToRoot.resolve( + (configuration.splitindex + ? DocPaths.INDEX_FILES.resolve(DocPaths.indexN(1)) + : DocPaths.INDEX_ALL)), + indexLabel, "", ""); + Content li = HtmlTree.LI(linkContent); + return li; + } + + /** + * Get help file link. If user has provided a help file, then generate a + * link to the user given file, which is already copied to current or + * destination directory. + * + * @return a content tree for the link + */ + protected Content getNavLinkHelp() { + String helpfile = configuration.helpfile; + DocPath helpfilenm; + if (helpfile.isEmpty()) { + helpfilenm = DocPaths.HELP_DOC; + } else { + DocFile file = DocFile.createFileForInput(configuration, helpfile); + helpfilenm = DocPath.create(file.getName()); + } + Content linkContent = getHyperLink(pathToRoot.resolve(helpfilenm), + helpLabel, "", ""); + Content li = HtmlTree.LI(linkContent); + return li; + } + + /** + * Get summary table header. + * + * @param header the header for the table + * @param scope the scope of the headers + * @return a content tree for the header + */ + public Content getSummaryTableHeader(List header, String scope) { + Content tr = new HtmlTree(HtmlTag.TR); + final int size = header.size(); + Content tableHeader; + if (size == 1) { + tableHeader = new StringContent(header.get(0)); + tr.addContent(HtmlTree.TH(HtmlStyle.colOne, scope, tableHeader)); + return tr; + } + for (int i = 0; i < size; i++) { + tableHeader = new StringContent(header.get(i)); + if(i == 0) + tr.addContent(HtmlTree.TH(HtmlStyle.colFirst, scope, tableHeader)); + else if(i == (size - 1)) + tr.addContent(HtmlTree.TH(HtmlStyle.colLast, scope, tableHeader)); + else + tr.addContent(HtmlTree.TH(scope, tableHeader)); + } + return tr; + } + + /** + * Get table caption. + * + * @param rawText the caption for the table which could be raw Html + * @return a content tree for the caption + */ + public Content getTableCaption(Content title) { + Content captionSpan = HtmlTree.SPAN(title); + Content space = getSpace(); + Content tabSpan = HtmlTree.SPAN(HtmlStyle.tabEnd, space); + Content caption = HtmlTree.CAPTION(captionSpan); + caption.addContent(tabSpan); + return caption; + } + + /** + * Get the marker anchor which will be added to the documentation tree. + * + * @param anchorName the anchor name attribute + * @return a content tree for the marker anchor + */ + public Content getMarkerAnchor(String anchorName) { + return getMarkerAnchor(getName(anchorName), null); + } + + /** + * Get the marker anchor which will be added to the documentation tree. + * + * @param sectionName the section name anchor attribute for page + * @return a content tree for the marker anchor + */ + public Content getMarkerAnchor(SectionName sectionName) { + return getMarkerAnchor(sectionName.getName(), null); + } + + /** + * Get the marker anchor which will be added to the documentation tree. + * + * @param sectionName the section name anchor attribute for page + * @param anchorName the anchor name combined with section name attribute for the page + * @return a content tree for the marker anchor + */ + public Content getMarkerAnchor(SectionName sectionName, String anchorName) { + return getMarkerAnchor(sectionName.getName() + getName(anchorName), null); + } + + /** + * Get the marker anchor which will be added to the documentation tree. + * + * @param anchorName the anchor name or id attribute + * @param anchorContent the content that should be added to the anchor + * @return a content tree for the marker anchor + */ + public Content getMarkerAnchor(String anchorName, Content anchorContent) { + if (anchorContent == null) + anchorContent = new Comment(" "); + Content markerAnchor = HtmlTree.A(configuration.htmlVersion, anchorName, anchorContent); + return markerAnchor; + } + + /** + * Returns a packagename content. + * + * @param packageElement the package to check + * @return package name content + */ + public Content getPackageName(PackageElement packageElement) { + return packageElement == null || packageElement.isUnnamed() + ? defaultPackageLabel + : getPackageLabel(packageElement.getQualifiedName().toString()); + } + + /** + * Returns a package name label. + * + * @param packageName the package name + * @return the package name content + */ + public Content getPackageLabel(String packageName) { + return new StringContent(packageName); + } + + /** + * Add package deprecation information to the documentation tree + * + * @param deprPkgs list of deprecated packages + * @param headingKey the caption for the deprecated package table + * @param tableSummary the summary for the deprecated package table + * @param tableHeader table headers for the deprecated package table + * @param contentTree the content tree to which the deprecated package table will be added + */ + protected void addPackageDeprecatedAPI(SortedSet deprPkgs, String headingKey, + String tableSummary, List tableHeader, Content contentTree) { + if (deprPkgs.size() > 0) { + Content caption = getTableCaption(configuration.getResource(headingKey)); + Content table = (configuration.isOutputHtml5()) + ? HtmlTree.TABLE(HtmlStyle.deprecatedSummary, caption) + : HtmlTree.TABLE(HtmlStyle.deprecatedSummary, tableSummary, caption); + table.addContent(getSummaryTableHeader(tableHeader, "col")); + Content tbody = new HtmlTree(HtmlTag.TBODY); + boolean altColor = true; + for (Element e : deprPkgs) { + PackageElement pkg = (PackageElement) e; + HtmlTree td = HtmlTree.TD(HtmlStyle.colOne, + getPackageLink(pkg, getPackageName(pkg))); + List tags = utils.getDeprecatedTrees(pkg); + if (!tags.isEmpty()) { + addInlineDeprecatedComment(pkg, tags.get(0), td); + } + HtmlTree tr = HtmlTree.TR(td); + tr.addStyle(altColor ? HtmlStyle.altColor : HtmlStyle.rowColor); + altColor = !altColor; + tbody.addContent(tr); + } + table.addContent(tbody); + Content li = HtmlTree.LI(HtmlStyle.blockList, table); + Content ul = HtmlTree.UL(HtmlStyle.blockList, li); + contentTree.addContent(ul); + } + } + + /** + * Return the path to the class page for a typeElement. + * + * @param te TypeElement for which the path is requested. + * @param name Name of the file(doesn't include path). + */ + protected DocPath pathString(TypeElement te, DocPath name) { + return pathString(utils.containingPackage(te), name); + } + + /** + * Return path to the given file name in the given package. So if the name + * passed is "Object.html" and the name of the package is "java.lang", and + * if the relative path is "../.." then returned string will be + * "../../java/lang/Object.html" + * + * @param packageElement Package in which the file name is assumed to be. + * @param name File name, to which path string is. + */ + protected DocPath pathString(PackageElement packageElement, DocPath name) { + return pathToRoot.resolve(DocPath.forPackage(packageElement).resolve(name)); + } + + /** + * Given a package, return the name to be used in HTML anchor tag. + * @param packageElement the package. + * @return the name to be used in HTML anchor tag. + */ + public String getPackageAnchorName(PackageElement packageElement) { + return packageElement == null || packageElement.isUnnamed() + ? SectionName.UNNAMED_PACKAGE_ANCHOR.getName() + : utils.getPackageName(packageElement); + } + + /** + * Return the link to the given package. + * + * @param packageElement the package to link to. + * @param label the label for the link. + * @return a content tree for the package link. + */ + public Content getPackageLink(PackageElement packageElement, String label) { + return getPackageLink(packageElement, new StringContent(label)); + } + + public Content getPackageLink(PackageElement packageElement) { + StringContent content = packageElement.isUnnamed() + ? new StringContent() + : new StringContent(utils.getPackageName(packageElement)); + return getPackageLink(packageElement, content); + } + + /** + * Return the link to the given package. + * + * @param packageElement the package to link to. + * @param label the label for the link. + * @return a content tree for the package link. + */ + public Content getPackageLink(PackageElement packageElement, Content label) { + boolean included = packageElement != null && utils.isIncluded(packageElement); + if (!included) { + for (PackageElement p : configuration.packages) { + if (p.equals(packageElement)) { + included = true; + break; + } + } + } + if (included || packageElement == null) { + return getHyperLink(pathString(packageElement, DocPaths.PACKAGE_SUMMARY), + label); + } else { + DocLink crossPkgLink = getCrossPackageLink(utils.getPackageName(packageElement)); + if (crossPkgLink != null) { + return getHyperLink(crossPkgLink, label); + } else { + return label; + } + } + } + + public Content interfaceName(TypeElement typeElement, boolean qual) { + Content name = new StringContent((qual) + ? typeElement.getQualifiedName().toString() + : utils.getSimpleName(typeElement)); + return (utils.isInterface(typeElement)) ? HtmlTree.SPAN(HtmlStyle.interfaceName, name) : name; + } + + /** + * Add the link to the content tree. + * + * @param typeElement program element typeElement for which the link will be added + * @param label label for the link + * @param htmltree the content tree to which the link will be added + */ + public void addSrcLink(Element typeElement, Content label, Content htmltree) { + if (typeElement == null) { + return; + } + TypeElement te = utils.getEnclosingTypeElement(typeElement); + if (te == null) { + // must be a typeElement since in has no containing class. + te = (TypeElement) typeElement; + } + DocPath href = pathToRoot + .resolve(DocPaths.SOURCE_OUTPUT) + .resolve(DocPath.forClass(utils, te)); + Content linkContent = getHyperLink(href + .fragment(SourceToHTMLConverter.getAnchorName(utils, typeElement)), label, "", ""); + htmltree.addContent(linkContent); + } + + /** + * Return the link to the given class. + * + * @param linkInfo the information about the link. + * + * @return the link for the given class. + */ + public Content getLink(LinkInfoImpl linkInfo) { + LinkFactoryImpl factory = new LinkFactoryImpl(this); + return factory.getLink(linkInfo); + } + + /** + * Return the type parameters for the given class. + * + * @param linkInfo the information about the link. + * @return the type for the given class. + */ + public Content getTypeParameterLinks(LinkInfoImpl linkInfo) { + LinkFactoryImpl factory = new LinkFactoryImpl(this); + return factory.getTypeParameterLinks(linkInfo, false); + } + + /************************************************************* + * Return a class cross link to external class documentation. + * The name must be fully qualified to determine which package + * the class is in. The -link option does not allow users to + * link to external classes in the "default" package. + * + * @param qualifiedClassName the qualified name of the external class. + * @param refMemName the name of the member being referenced. This should + * be null or empty string if no member is being referenced. + * @param label the label for the external link. + * @param strong true if the link should be strong. + * @param style the style of the link. + * @param code true if the label should be code font. + */ + public Content getCrossClassLink(String qualifiedClassName, String refMemName, + Content label, boolean strong, String style, + boolean code) { + String className = ""; + String packageName = qualifiedClassName == null ? "" : qualifiedClassName; + int periodIndex; + while ((periodIndex = packageName.lastIndexOf('.')) != -1) { + className = packageName.substring(periodIndex + 1, packageName.length()) + + (className.length() > 0 ? "." + className : ""); + Content defaultLabel = new StringContent(className); + if (code) + defaultLabel = HtmlTree.CODE(defaultLabel); + packageName = packageName.substring(0, periodIndex); + if (getCrossPackageLink(packageName) != null) { + /* + The package exists in external documentation, so link to the external + class (assuming that it exists). This is definitely a limitation of + the -link option. There are ways to determine if an external package + exists, but no way to determine if the external class exists. We just + have to assume that it does. + */ + DocLink link = configuration.extern.getExternalLink(packageName, pathToRoot, + className + ".html", refMemName); + return getHyperLink(link, + (label == null) || label.isEmpty() ? defaultLabel : label, + strong, style, + configuration.getText("doclet.Href_Class_Or_Interface_Title", packageName), + ""); + } + } + return null; + } + + public boolean isClassLinkable(TypeElement typeElement) { + if (utils.isIncluded(typeElement)) { + return configuration.isGeneratedDoc(typeElement); + } + return configuration.extern.isExternal(typeElement); + } + + public DocLink getCrossPackageLink(String pkgName) { + return configuration.extern.getExternalLink(pkgName, pathToRoot, + DocPaths.PACKAGE_SUMMARY.getPath()); + } + + /** + * Get the class link. + * + * @param context the id of the context where the link will be added + * @param element to link to + * @return a content tree for the link + */ + public Content getQualifiedClassLink(LinkInfoImpl.Kind context, Element element) { + LinkInfoImpl linkInfoImpl = new LinkInfoImpl(configuration, context, (TypeElement)element); + return getLink(linkInfoImpl.label(utils.getFullyQualifiedName(element))); + } + + /** + * Add the class link. + * + * @param context the id of the context where the link will be added + * @param typeElement to link to + * @param contentTree the content tree to which the link will be added + */ + public void addPreQualifiedClassLink(LinkInfoImpl.Kind context, TypeElement typeElement, Content contentTree) { + addPreQualifiedClassLink(context, typeElement, false, contentTree); + } + + /** + * Retrieve the class link with the package portion of the label in + * plain text. If the qualifier is excluded, it will not be included in the + * link label. + * + * @param typeElement the class to link to. + * @param isStrong true if the link should be strong. + * @return the link with the package portion of the label in plain text. + */ + public Content getPreQualifiedClassLink(LinkInfoImpl.Kind context, + TypeElement typeElement, boolean isStrong) { + ContentBuilder classlink = new ContentBuilder(); + PackageElement pkg = utils.containingPackage(typeElement); + if (pkg != null && ! configuration.shouldExcludeQualifier(pkg.getSimpleName().toString())) { + classlink.addContent(getEnclosingPackageName(typeElement)); + } + classlink.addContent(getLink(new LinkInfoImpl(configuration, + context, typeElement).label(utils.getSimpleName(typeElement)).strong(isStrong))); + return classlink; + } + + /** + * Add the class link with the package portion of the label in + * plain text. If the qualifier is excluded, it will not be included in the + * link label. + * + * @param context the id of the context where the link will be added + * @param typeElement the class to link to + * @param isStrong true if the link should be strong + * @param contentTree the content tree to which the link with be added + */ + public void addPreQualifiedClassLink(LinkInfoImpl.Kind context, + TypeElement typeElement, boolean isStrong, Content contentTree) { + PackageElement pkg = utils.containingPackage(typeElement); + if(pkg != null && ! configuration.shouldExcludeQualifier(pkg.getSimpleName().toString())) { + contentTree.addContent(getEnclosingPackageName(typeElement)); + } + LinkInfoImpl linkinfo = new LinkInfoImpl(configuration, context, typeElement) + .label(utils.getSimpleName(typeElement)) + .strong(isStrong); + Content link = getLink(linkinfo); + contentTree.addContent(link); + } + + /** + * Add the class link, with only class name as the strong link and prefixing + * plain package name. + * + * @param context the id of the context where the link will be added + * @param typeElement the class to link to + * @param contentTree the content tree to which the link with be added + */ + public void addPreQualifiedStrongClassLink(LinkInfoImpl.Kind context, TypeElement typeElement, Content contentTree) { + addPreQualifiedClassLink(context, typeElement, true, contentTree); + } + + /** + * Get the link for the given member. + * + * @param context the id of the context where the link will be added + * @param element the member being linked to + * @param label the label for the link + * @return a content tree for the element link + */ + public Content getDocLink(LinkInfoImpl.Kind context, Element element, String label) { + return getDocLink(context, utils.getEnclosingTypeElement(element), element, + new StringContent(label)); + } + + /** + * Return the link for the given member. + * + * @param context the id of the context where the link will be printed. + * @param element the member being linked to. + * @param label the label for the link. + * @param strong true if the link should be strong. + * @return the link for the given member. + */ + public Content getDocLink(LinkInfoImpl.Kind context, Element element, String label, + boolean strong) { + return getDocLink(context, utils.getEnclosingTypeElement(element), element, label, strong); + } + + /** + * Return the link for the given member. + * + * @param context the id of the context where the link will be printed. + * @param typeElement the typeElement that we should link to. This is not + necessarily equal to element.containingClass(). We may be + inheriting comments. + * @param element the member being linked to. + * @param label the label for the link. + * @param strong true if the link should be strong. + * @return the link for the given member. + */ + public Content getDocLink(LinkInfoImpl.Kind context, TypeElement typeElement, Element element, + String label, boolean strong) { + return getDocLink(context, typeElement, element, label, strong, false); + } + + public Content getDocLink(LinkInfoImpl.Kind context, TypeElement typeElement, Element element, + Content label, boolean strong) { + return getDocLink(context, typeElement, element, label, strong, false); + } + + /** + * Return the link for the given member. + * + * @param context the id of the context where the link will be printed. + * @param typeElement the typeElement that we should link to. This is not + necessarily equal to element.containingClass(). We may be + inheriting comments. + * @param element the member being linked to. + * @param label the label for the link. + * @param strong true if the link should be strong. + * @param isProperty true if the element parameter is a JavaFX property. + * @return the link for the given member. + */ + public Content getDocLink(LinkInfoImpl.Kind context, TypeElement typeElement, Element element, + String label, boolean strong, boolean isProperty) { + return getDocLink(context, typeElement, element, new StringContent(check(label)), strong, isProperty); + } + + String check(String s) { + if (s.matches(".*[&<>].*")) { + throw new IllegalArgumentException(s); + } + return s; + } + + public Content getDocLink(LinkInfoImpl.Kind context, TypeElement typeElement, Element element, + Content label, boolean strong, boolean isProperty) { + if (! (utils.isIncluded(element) || utils.isLinkable(typeElement))) { + return label; + } else if (utils.isExecutableElement(element)) { + ExecutableElement ee = (ExecutableElement)element; + return getLink(new LinkInfoImpl(configuration, context, typeElement) + .label(label) + .where(getName(getAnchor(ee, isProperty))) + .strong(strong)); + } else if (utils.isVariableElement(element) || utils.isTypeElement(element)) { + return getLink(new LinkInfoImpl(configuration, context, typeElement) + .label(label) + .where(getName(element.getSimpleName().toString())) + .strong(strong)); + } else { + return label; + } + } + + /** + * Return the link for the given member. + * + * @param context the id of the context where the link will be added + * @param typeElement the typeElement that we should link to. This is not + necessarily equal to element.containingClass(). We may be + inheriting comments + * @param element the member being linked to + * @param label the label for the link + * @return the link for the given member + */ + public Content getDocLink(LinkInfoImpl.Kind context, TypeElement typeElement, Element element, + Content label) { + if (! (utils.isIncluded(element) || utils.isLinkable(typeElement))) { + return label; + } else if (utils.isExecutableElement(element)) { + ExecutableElement emd = (ExecutableElement) element; + return getLink(new LinkInfoImpl(configuration, context, typeElement) + .label(label) + .where(getName(getAnchor(emd)))); + } else if (utils.isVariableElement(element) || utils.isTypeElement(element)) { + return getLink(new LinkInfoImpl(configuration, context, typeElement) + .label(label).where(getName(element.getSimpleName().toString()))); + } else { + return label; + } + } + + public String getAnchor(ExecutableElement executableElement) { + return getAnchor(executableElement, false); + } + + public String getAnchor(ExecutableElement executableElement, boolean isProperty) { + if (isProperty) { + return executableElement.getSimpleName().toString(); + } + String signature = utils.signature(executableElement); + StringBuilder signatureParsed = new StringBuilder(); + int counter = 0; + for (int i = 0; i < signature.length(); i++) { + char c = signature.charAt(i); + if (c == '<') { + counter++; + } else if (c == '>') { + counter--; + } else if (counter == 0) { + signatureParsed.append(c); + } + } + return utils.getSimpleName(executableElement) + signatureParsed.toString(); + } + + public Content seeTagToContent(Element element, DocTree see) { + + Kind kind = see.getKind(); + if (!(kind == LINK || kind == SEE || kind == LINK_PLAIN)) { + return new ContentBuilder(); + } + + CommentHelper ch = utils.getCommentHelper(element); + String tagName = ch.getTagName(see); + String seetext = replaceDocRootDir(utils.normalizeNewlines(ch.getText(see))); + // Check if @see is an href or "string" + if (seetext.startsWith("<") || seetext.startsWith("\"")) { + return new RawHtml(seetext); + } + boolean isLinkPlain = kind == LINK_PLAIN; + Content label = plainOrCode(isLinkPlain, new RawHtml(ch.getLabel(configuration, see))); + + //The text from the @see tag. We will output this text when a label is not specified. + Content text = plainOrCode(kind == LINK_PLAIN, new RawHtml(seetext)); + + TypeElement refClass = ch.getReferencedClass(configuration, see); + String refClassName = ch.getReferencedClassName(configuration, see); + Element refMem = ch.getReferencedMember(configuration, see); + String refMemName = ch.getReferencedMemberName(see); + + if (refMemName == null && refMem != null) { + refMemName = refMem.toString(); + } + if (refClass == null) { + //@see is not referencing an included class + PackageElement refPackage = ch.getReferencedPackage(configuration, see); + if (refPackage != null && utils.isIncluded(refPackage)) { + //@see is referencing an included package + if (label.isEmpty()) + label = plainOrCode(isLinkPlain, + new StringContent(refPackage.getQualifiedName().toString())); + return getPackageLink(refPackage, label); + } else { + // @see is not referencing an included class or package. Check for cross links. + Content classCrossLink; + DocLink packageCrossLink = getCrossPackageLink(refClassName); + if (packageCrossLink != null) { + // Package cross link found + return getHyperLink(packageCrossLink, + (label.isEmpty() ? text : label)); + } else if ((classCrossLink = getCrossClassLink(refClassName, + refMemName, label, false, "", !isLinkPlain)) != null) { + // Class cross link found (possibly to a member in the class) + return classCrossLink; + } else { + // No cross link found so print warning + configuration.getDocletSpecificMsg().warning(ch.getDocTreePath(see), + "doclet.see.class_or_package_not_found", + "@" + tagName, + seetext); + return (label.isEmpty() ? text: label); + } + } + } else if (refMemName == null) { + // Must be a class reference since refClass is not null and refMemName is null. + if (label.isEmpty()) { + /* + * it seems to me this is the right thing to do, but it causes comparator failures. + */ + if (!configuration.backwardCompatibility) { + StringContent content = utils.isEnclosingPackageIncluded(refClass) + ? new StringContent(utils.getSimpleName(refClass)) + : new StringContent(utils.getFullyQualifiedName(refClass)); + label = plainOrCode(isLinkPlain, content); + } else { + label = plainOrCode(isLinkPlain, + new StringContent(utils.getSimpleName(refClass))); + } + + } + return getLink(new LinkInfoImpl(configuration, LinkInfoImpl.Kind.DEFAULT, refClass) + .label(label)); + } else if (refMem == null) { + // Must be a member reference since refClass is not null and refMemName is not null. + // However, refMem is null, so this referenced member does not exist. + return (label.isEmpty() ? text: label); + } else { + // Must be a member reference since refClass is not null and refMemName is not null. + // refMem is not null, so this @see tag must be referencing a valid member. + TypeElement containing = utils.getEnclosingTypeElement(refMem); + if (ch.getText(see).trim().startsWith("#") && + ! (utils.isPublic(containing) || utils.isLinkable(containing))) { + // Since the link is relative and the holder is not even being + // documented, this must be an inherited link. Redirect it. + // The current class either overrides the referenced member or + // inherits it automatically. + if (this instanceof ClassWriterImpl) { + containing = ((ClassWriterImpl) this).getTypeElement(); + } else if (!utils.isPublic(containing)) { + configuration.getDocletSpecificMsg().warning( + ch.getDocTreePath(see), "doclet.see.class_or_package_not_accessible", + tagName, utils.getFullyQualifiedName(containing)); + } else { + configuration.getDocletSpecificMsg().warning( + ch.getDocTreePath(see), "doclet.see.class_or_package_not_found", + tagName, seetext); + } + } + if (configuration.currentTypeElement != containing) { + refMemName = (utils.isConstructor(refMem)) + ? refMemName + : utils.getSimpleName(containing) + "." + refMemName; + } + if (utils.isExecutableElement(refMem)) { + if (refMemName.indexOf('(') < 0) { + refMemName += utils.makeSignature((ExecutableElement)refMem, true); + } + } + + text = plainOrCode(kind == LINK_PLAIN, new StringContent(refMemName)); + + return getDocLink(LinkInfoImpl.Kind.SEE_TAG, containing, + refMem, (label.isEmpty() ? text: label), false); + } + } + + private Content plainOrCode(boolean plain, Content body) { + return (plain || body.isEmpty()) ? body : HtmlTree.CODE(body); + } + + /** + * Add the inline comment. + * + * @param element the Element for which the inline comment will be added + * @param tag the inline tag to be added + * @param htmltree the content tree to which the comment will be added + */ + public void addInlineComment(Element element, DocTree tag, Content htmltree) { + CommentHelper ch = utils.getCommentHelper(element); + List description = ch.getDescription(configuration, tag); + addCommentTags(element, tag, description, false, false, htmltree); + } + + /** + * Add the inline deprecated comment. + * + * @param e the Element for which the inline deprecated comment will be added + * @param tag the inline tag to be added + * @param htmltree the content tree to which the comment will be added + */ + public void addInlineDeprecatedComment(Element e, DocTree tag, Content htmltree) { + CommentHelper ch = utils.getCommentHelper(e); + addCommentTags(e, ch.getBody(configuration, tag), true, false, htmltree); + } + + /** + * Adds the summary content. + * + * @param element the Element for which the summary will be generated + * @param htmltree the documentation tree to which the summary will be added + */ + public void addSummaryComment(Element element, Content htmltree) { + addSummaryComment(element, utils.getFirstSentenceTrees(element), htmltree); + } + + /** + * Adds the summary content. + * + * @param element the Element for which the summary will be generated + * @param firstSentenceTags the first sentence tags for the doc + * @param htmltree the documentation tree to which the summary will be added + */ + public void addSummaryComment(Element element, List firstSentenceTags, Content htmltree) { + addCommentTags(element, firstSentenceTags, false, true, htmltree); + } + + public void addSummaryDeprecatedComment(Element element, DocTree tag, Content htmltree) { + CommentHelper ch = utils.getCommentHelper(element); + List body = ch.getBody(configuration, tag); + addCommentTags(element, ch.getFirstSentenceTrees(configuration, body), true, true, htmltree); + } + + /** + * Adds the inline comment. + * + * @param element the Element for which the inline comments will be generated + * @param htmltree the documentation tree to which the inline comments will be added + */ + public void addInlineComment(Element element, Content htmltree) { + addCommentTags(element, utils.getBody(element), false, false, htmltree); + } + + /** + * Adds the comment tags. + * + * @param element the Element for which the comment tags will be generated + * @param tags the first sentence tags for the doc + * @param depr true if it is deprecated + * @param first true if the first sentence tags should be added + * @param htmltree the documentation tree to which the comment tags will be added + */ + private void addCommentTags(Element element, List tags, boolean depr, + boolean first, Content htmltree) { + addCommentTags(element, null, tags, depr, first, htmltree); + } + + /** + * Adds the comment tags. + * + * @param element for which the comment tags will be generated + * @param holderTag the block tag context for the inline tags + * @param tags the first sentence tags for the doc + * @param depr true if it is deprecated + * @param first true if the first sentence tags should be added + * @param htmltree the documentation tree to which the comment tags will be added + */ + private void addCommentTags(Element element, DocTree holderTag, List tags, boolean depr, + boolean first, Content htmltree) { + if(configuration.nocomment){ + return; + } + Content div; + Content result = commentTagsToContent(null, element, tags, first); + if (depr) { + Content italic = HtmlTree.SPAN(HtmlStyle.deprecationComment, result); + div = HtmlTree.DIV(HtmlStyle.block, italic); + htmltree.addContent(div); + } + else { + div = HtmlTree.DIV(HtmlStyle.block, result); + htmltree.addContent(div); + } + if (tags.isEmpty()) { + htmltree.addContent(getSpace()); + } + } + + boolean ignoreNonInlineTag(DocTree dtree) { + Name name = null; + if (dtree.getKind() == Kind.START_ELEMENT) { + StartElementTree setree = (StartElementTree)dtree; + name = setree.getName(); + } else if (dtree.getKind() == Kind.END_ELEMENT) { + EndElementTree eetree = (EndElementTree)dtree; + name = eetree.getName(); + } + + if (name != null) { + com.sun.tools.doclint.HtmlTag htmlTag = com.sun.tools.doclint.HtmlTag.get(name); + if (htmlTag != null && + htmlTag.blockType != com.sun.tools.doclint.HtmlTag.BlockType.INLINE) { + return true; + } + } + return false; + } + + boolean isAllWhiteSpace(String body) { + for (int i = 0 ; i < body.length(); i++) { + if (!Character.isWhitespace(body.charAt(i))) + return false; + } + return true; + } + + /** + * Converts inline tags and text to text strings, expanding the + * inline tags along the way. Called wherever text can contain + * an inline tag, such as in comments or in free-form text arguments + * to non-inline tags. + * + * @param holderTag specific tag where comment resides + * @param element specific element where comment resides + * @param tags array of text tags and inline tags (often alternating) + present in the text of interest for this element + * @param isFirstSentence true if text is first sentence + * @return a Content object + */ + public Content commentTagsToContent(DocTree holderTag, Element element, + List tags, boolean isFirstSentence) { + + final Content result = new ContentBuilder() { + @Override + public void addContent(String text) { + super.addContent(utils.normalizeNewlines(text)); + } + }; + CommentHelper ch = utils.getCommentHelper(element); + // Array of all possible inline tags for this javadoc run + configuration.tagletManager.checkTags(utils, element, tags, true); + for (ListIterator iterator = tags.listIterator(); iterator.hasNext();) { + DocTree tag = iterator.next(); + // zap block tags + if (isFirstSentence && ignoreNonInlineTag(tag)) + continue; + + if (isFirstSentence && iterator.nextIndex() == tags.size() && + (tag.getKind() == TEXT && isAllWhiteSpace(ch.getText(tag)))) + continue; + + boolean allDone = new SimpleDocTreeVisitor() { + // notify the next DocTree handler to take necessary action + boolean commentRemoved = false; + + private boolean isLast(DocTree node) { + return node.equals(tags.get(tags.size() - 1)); + } + + private boolean isFirst(DocTree node) { + return node.equals(tags.get(0)); + } + + private boolean inAnAtag() { + if (utils.isStartElement(tag)) { + StartElementTree st = (StartElementTree)tag; + Name name = st.getName(); + if (name != null) { + com.sun.tools.doclint.HtmlTag htag = + com.sun.tools.doclint.HtmlTag.get(name); + return htag != null && htag.equals(com.sun.tools.doclint.HtmlTag.A); + } + } + return false; + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public Boolean visitAttribute(AttributeTree node, Content c) { + StringBuilder sb = new StringBuilder(SPACER).append(node.getName()); + if (node.getValueKind() == ValueKind.EMPTY) { + result.addContent(sb.toString()); + return false; + } + sb.append("="); + String quote; + switch (node.getValueKind()) { + case DOUBLE: + quote = "\""; + break; + case SINGLE: + quote = "\'"; + break; + default: + quote = ""; + break; + } + sb.append(quote); + result.addContent(sb.toString()); + Content docRootContent = new ContentBuilder(); + + for (DocTree dt : node.getValue()) { + if (utils.isText(dt) && inAnAtag()) { + String text = ((TextTree) dt).getBody(); + if (text.startsWith("/..") && !configuration.docrootparent.isEmpty()) { + result.addContent(configuration.docrootparent); + docRootContent = new ContentBuilder(); + text = textCleanup(text.substring(3), isLast(node)); + } else { + if (!docRootContent.isEmpty()) { + docRootContent = copyDocRootContent(docRootContent); + } else { + text = redirectRelativeLinks(element, (TextTree) dt); + } + text = textCleanup(text, isLast(node)); + } + result.addContent(text); + } else { + docRootContent = copyDocRootContent(docRootContent); + dt.accept(this, docRootContent); + } + } + copyDocRootContent(docRootContent); + result.addContent(quote); + return false; + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public Boolean visitComment(CommentTree node, Content c) { + if (isFirstSentence && isFirst(node)) { + commentRemoved = true; + return this.visit(iterator.next(), c); + } + result.addContent(new RawHtml(node.getBody())); + return false; + } + + private Content copyDocRootContent(Content content) { + if (!content.isEmpty()) { + result.addContent(content); + return new ContentBuilder(); + } + return content; + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public Boolean visitDocRoot(DocRootTree node, Content c) { + Content docRootContent = TagletWriter.getInlineTagOutput(element, + configuration.tagletManager, + holderTag, + node, + getTagletWriterInstance(isFirstSentence)); + if (c != null) { + c.addContent(docRootContent); + } else { + result.addContent(docRootContent); + } + return false; + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public Boolean visitEndElement(EndElementTree node, Content c) { + RawHtml rawHtml = new RawHtml(""); + result.addContent(rawHtml); + return false; + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public Boolean visitEntity(EntityTree node, Content c) { + result.addContent(new RawHtml(node.toString())); + return false; + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public Boolean visitErroneous(ErroneousTree node, Content c) { + configuration.getDocletSpecificMsg().warning(ch.getDocTreePath(node), + "doclet.tag.invalid_usage", node); + result.addContent(new RawHtml(node.toString())); + return false; + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public Boolean visitInheritDoc(InheritDocTree node, Content c) { + Content output = TagletWriter.getInlineTagOutput(element, + configuration.tagletManager, holderTag, + tag, getTagletWriterInstance(isFirstSentence)); + result.addContent(output); + // if we obtained the first sentence successfully, nothing more to do + return (isFirstSentence && !output.isEmpty()); + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public Boolean visitIndex(IndexTree node, Content p) { + Content output = TagletWriter.getInlineTagOutput(element, + configuration.tagletManager, holderTag, tag, + getTagletWriterInstance(isFirstSentence)); + if (output != null) { + result.addContent(output); + } + return false; + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public Boolean visitLink(LinkTree node, Content c) { + // we need to pass the DocTreeImpl here, so ignore node + result.addContent(seeTagToContent(element, tag)); + return false; + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public Boolean visitLiteral(LiteralTree node, Content c) { + String s = node.getBody().toString(); + Content content = new StringContent(utils.normalizeNewlines(s)); + if (node.getKind() == CODE) + content = HtmlTree.CODE(content); + result.addContent(content); + return false; + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public Boolean visitSee(SeeTree node, Content c) { + // we need to pass the DocTreeImpl here, so ignore node + result.addContent(seeTagToContent(element, tag)); + return false; + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public Boolean visitStartElement(StartElementTree node, Content c) { + String text = "<" + node.getName(); + text = utils.normalizeNewlines(text); + RawHtml rawHtml = new RawHtml(text); + result.addContent(rawHtml); + + for (DocTree dt : node.getAttributes()) { + dt.accept(this, null); + } + result.addContent(new RawHtml(node.isSelfClosing() ? "/>" : ">")); + return false; + } + + private String textCleanup(String text, boolean isLast) { + return textCleanup(text, isLast, false); + } + + private String textCleanup(String text, boolean isLast, boolean trimLeader) { + if (trimLeader) { + text = removeLeadingWhitespace(text); + } + if (isFirstSentence && isLast) { + text = removeTrailingWhitespace(text); + } + text = utils.replaceTabs(text); + text = utils.normalizeNewlines(text); + return text; + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public Boolean visitText(TextTree node, Content c) { + String text = node.getBody(); + text = textCleanup(text, isLast(node), commentRemoved); + commentRemoved = false; + result.addContent(new RawHtml(text)); + return false; + } + + @Override @DefinedBy(Api.COMPILER_TREE) + protected Boolean defaultAction(DocTree node, Content c) { + Content output = TagletWriter.getInlineTagOutput(element, + configuration.tagletManager, holderTag, tag, + getTagletWriterInstance(isFirstSentence)); + if (output != null) { + result.addContent(output); + } + return false; + } + + }.visit(tag, null); + if (allDone) + break; + } + return result; + } + + private String removeTrailingWhitespace(String text) { + char[] buf = text.toCharArray(); + for (int i = buf.length - 1; i > 0 ; i--) { + if (!Character.isWhitespace(buf[i])) + return text.substring(0, i + 1); + } + return text; + } + + private String removeLeadingWhitespace(String text) { + char[] buf = text.toCharArray(); + for (int i = 0; i < buf.length; i++) { + if (!Character.isWhitespace(buf[i])) { + return text.substring(i); + } + } + return text; + } + + /** + * Return true if relative links should not be redirected. + * + * @return Return true if a relative link should not be redirected. + */ + private boolean shouldNotRedirectRelativeLinks() { + return this instanceof AnnotationTypeWriter || + this instanceof ClassWriter || + this instanceof PackageSummaryWriter; + } + + /** + * Suppose a piece of documentation has a relative link. When you copy + * that documentation to another place such as the index or class-use page, + * that relative link will no longer work. We should redirect those links + * so that they will work again. + *

+ * Here is the algorithm used to fix the link: + *

+ * {@literal => docRoot + + } + *

+ * For example, suppose DocletEnvironment has this link: + * {@literal The package Page } + *

+ * If this link appeared in the index, we would redirect + * the link like this: + * + * {@literal The package Page} + * + * @param element the Element object whose documentation is being written. + * @param text the text being written. + * + * @return the text, with all the relative links redirected to work. + */ + private String redirectRelativeLinks(Element element, TextTree tt) { + String text = tt.getBody(); + if (element == null || utils.isOverviewElement(element) || shouldNotRedirectRelativeLinks()) { + return text; + } + + DocPath redirectPathFromRoot = new SimpleElementVisitor9() { + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public DocPath visitType(TypeElement e, Void p) { + return DocPath.forPackage(utils.containingPackage(e)); + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public DocPath visitPackage(PackageElement e, Void p) { + return DocPath.forPackage(e); + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public DocPath visitVariable(VariableElement e, Void p) { + return DocPath.forPackage(utils.containingPackage(e)); + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public DocPath visitExecutable(ExecutableElement e, Void p) { + return DocPath.forPackage(utils.containingPackage(e)); + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + protected DocPath defaultAction(Element e, Void p) { + return null; + } + }.visit(element); + if (redirectPathFromRoot == null) { + return text; + } + String lower = Utils.toLowerCase(text); + if (!(lower.startsWith("mailto:") + || lower.startsWith("http:") + || lower.startsWith("https:") + || lower.startsWith("file:"))) { + text = "{@" + (new DocRootTaglet()).getName() + "}/" + + redirectPathFromRoot.resolve(text).getPath(); + text = replaceDocRootDir(text); + } + return text; + } + + static final Set blockTags = new HashSet<>(); + static { + for (HtmlTag t: HtmlTag.values()) { + if (t.blockType == HtmlTag.BlockType.BLOCK) + blockTags.add(t.value); + } + } + + /** + * Add a link to the stylesheet file. + * + * @param head the content tree to which the files will be added + */ + public void addStyleSheetProperties(Content head) { + String stylesheetfile = configuration.stylesheetfile; + DocPath stylesheet; + if (stylesheetfile.isEmpty()) { + stylesheet = DocPaths.STYLESHEET; + } else { + DocFile file = DocFile.createFileForInput(configuration, stylesheetfile); + stylesheet = DocPath.create(file.getName()); + } + HtmlTree link = HtmlTree.LINK("stylesheet", "text/css", + pathToRoot.resolve(stylesheet).getPath(), + "Style"); + head.addContent(link); + if (configuration.createindex) { + HtmlTree jq_link = HtmlTree.LINK("stylesheet", "text/css", + pathToRoot.resolve(DocPaths.JQUERY_FILES.resolve(DocPaths.JQUERY_STYLESHEET_FILE)).getPath(), + "Style"); + head.addContent(jq_link); + } + } + + /** + * Add a link to the JavaScript file. + * + * @param head the content tree to which the files will be added + */ + public void addScriptProperties(Content head) { + HtmlTree javascript = HtmlTree.SCRIPT(pathToRoot.resolve(DocPaths.JAVASCRIPT).getPath()); + head.addContent(javascript); + if (configuration.createindex) { + if (pathToRoot != null && script != null) { + String path = pathToRoot.isEmpty() ? "." : pathToRoot.getPath(); + script.addContent(new RawHtml("var pathtoroot = \"" + path + "/\";loadScripts(document, \'script\');")); + } + addJQueryFile(head, DocPaths.JSZIP_MIN); + addJQueryFile(head, DocPaths.JSZIPUTILS_MIN); + head.addContent(new RawHtml("")); + addJQueryFile(head, DocPaths.JQUERY_JS_1_10); + addJQueryFile(head, DocPaths.JQUERY_JS); + } + } + + /** + * Add a link to the JQuery javascript file. + * + * @param head the content tree to which the files will be added + * @param filePath the DocPath of the file that needs to be added + */ + private void addJQueryFile(Content head, DocPath filePath) { + HtmlTree jqyeryScriptFile = HtmlTree.SCRIPT( + pathToRoot.resolve(DocPaths.JQUERY_FILES.resolve(filePath)).getPath()); + head.addContent(jqyeryScriptFile); + } + + /** + * According to + * The Java™ Language Specification, + * all the outer classes and static nested classes are core classes. + */ + public boolean isCoreClass(TypeElement typeElement) { + return utils.getEnclosingTypeElement(typeElement) == null || utils.isStatic(typeElement); + } + + /** + * Adds the annotation types for the given packageElement. + * + * @param packageElement the package to write annotations for. + * @param htmltree the documentation tree to which the annotation info will be + * added + */ + public void addAnnotationInfo(PackageElement packageElement, Content htmltree) { + addAnnotationInfo(packageElement, packageElement.getAnnotationMirrors(), htmltree); + } + + /** + * Add the annotation types of the executable receiver. + * + * @param method the executable to write the receiver annotations for. + * @param descList list of annotation description. + * @param htmltree the documentation tree to which the annotation info will be + * added + */ + public void addReceiverAnnotationInfo(ExecutableElement method, List descList, + Content htmltree) { + addAnnotationInfo(0, method, descList, false, htmltree); + } + + /* + * this is a hack to delay dealing with Annotations in the writers, the assumption + * is that all necessary checks have been made to get here. + */ + public void addReceiverAnnotationInfo(ExecutableElement method, TypeMirror rcvrTypeMirror, + List annotationMirrors, Content htmltree) { + TypeMirror rcvrType = method.getReceiverType(); + List annotationMirrors1 = rcvrType.getAnnotationMirrors(); + addAnnotationInfo(0, method, annotationMirrors1, false, htmltree); + } + + /** + * Adds the annotatation types for the given element. + * + * @param element the package to write annotations for + * @param htmltree the content tree to which the annotation types will be added + */ + public void addAnnotationInfo(Element element, Content htmltree) { + addAnnotationInfo(element, element.getAnnotationMirrors(), htmltree); + } + + /** + * Add the annotatation types for the given element and parameter. + * + * @param indent the number of spaces to indent the parameters. + * @param element the element to write annotations for. + * @param param the parameter to write annotations for. + * @param tree the content tree to which the annotation types will be added + */ + public boolean addAnnotationInfo(int indent, Element element, VariableElement param, + Content tree) { + return addAnnotationInfo(indent, element, param.getAnnotationMirrors(), false, tree); + } + + /** + * Adds the annotatation types for the given Element. + * + * @param element the element to write annotations for. + * @param descList the array of {@link AnnotationDesc}. + * @param htmltree the documentation tree to which the annotation info will be + * added + */ + private void addAnnotationInfo(Element element, List descList, + Content htmltree) { + addAnnotationInfo(0, element, descList, true, htmltree); + } + + /** + * Adds the annotation types for the given element. + * + * @param indent the number of extra spaces to indent the annotations. + * @param element the element to write annotations for. + * @param descList the array of {@link AnnotationDesc}. + * @param htmltree the documentation tree to which the annotation info will be + * added + */ + private boolean addAnnotationInfo(int indent, Element element, + List descList, boolean lineBreak, Content htmltree) { + List annotations = getAnnotations(indent, descList, lineBreak); + String sep = ""; + if (annotations.isEmpty()) { + return false; + } + for (Content annotation: annotations) { + htmltree.addContent(sep); + htmltree.addContent(annotation); + if (!lineBreak) { + sep = " "; + } + } + return true; + } + + /** + * Return the string representations of the annotation types for + * the given doc. + * + * @param indent the number of extra spaces to indent the annotations. + * @param descList the array of {@link AnnotationDesc}. + * @param linkBreak if true, add new line between each member value. + * @return an array of strings representing the annotations being + * documented. + */ + private List getAnnotations(int indent, List descList, boolean linkBreak) { + return getAnnotations(indent, descList, linkBreak, true); + } + + private List getAnnotations(int indent, AnnotationMirror amirror, boolean linkBreak) { + List descList = new ArrayList<>(); + descList.add(amirror); + return getAnnotations(indent, descList, linkBreak, true); + } + + /** + * Return the string representations of the annotation types for + * the given doc. + * + * A {@code null} {@code elementType} indicates that all the + * annotations should be returned without any filtering. + * + * @param indent the number of extra spaces to indent the annotations. + * @param descList the array of {@link AnnotationDesc}. + * @param linkBreak if true, add new line between each member value. + * @param isJava5DeclarationLocation + * @return an array of strings representing the annotations being + * documented. + */ + public List getAnnotations(int indent, List descList, + boolean linkBreak, boolean isJava5DeclarationLocation) { + List results = new ArrayList<>(); + ContentBuilder annotation; + for (AnnotationMirror aDesc : descList) { + TypeElement annotationElement = (TypeElement)aDesc.getAnnotationType().asElement(); + // If an annotation is not documented, do not add it to the list. If + // the annotation is of a repeatable type, and if it is not documented + // and also if its container annotation is not documented, do not add it + // to the list. If an annotation of a repeatable type is not documented + // but its container is documented, it will be added to the list. + if (!utils.isDocumentedAnnotation(annotationElement) && + (!isAnnotationDocumented && !isContainerDocumented)) { + continue; + } + /* TODO: check logic here to correctly handle declaration + * and type annotations. + if (utils.isDeclarationAnnotation(annotationElement, isJava5DeclarationLocation)) { + continue; + }*/ + annotation = new ContentBuilder(); + isAnnotationDocumented = false; + LinkInfoImpl linkInfo = new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.ANNOTATION, annotationElement); + Map pairs = aDesc.getElementValues(); + // If the annotation is synthesized, do not print the container. + if (utils.configuration.workArounds.isSynthesized(aDesc)) { + for (ExecutableElement ee : pairs.keySet()) { + AnnotationValue annotationValue = pairs.get(ee); + List annotationTypeValues = new ArrayList<>(); + + new SimpleAnnotationValueVisitor9>() { + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Void visitArray(List vals, List p) { + p.addAll(vals); + return null; + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + protected Void defaultAction(Object o, List p) { + p.add(annotationValue); + return null; + } + }.visit(annotationValue, annotationTypeValues); + + String sep = ""; + for (AnnotationValue av : annotationTypeValues) { + annotation.addContent(sep); + annotation.addContent(annotationValueToContent(av)); + sep = " "; + } + } + } else if (isAnnotationArray(pairs)) { + // If the container has 1 or more value defined and if the + // repeatable type annotation is not documented, do not print + // the container. + if (pairs.size() == 1 && isAnnotationDocumented) { + List annotationTypeValues = new ArrayList<>(); + for (AnnotationValue a : pairs.values()) { + new SimpleAnnotationValueVisitor9>() { + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Void visitArray(List vals, List annotationTypeValues) { + for (AnnotationValue av : vals) { + annotationTypeValues.add(av); + } + return null; + } + }.visit(a, annotationTypeValues); + } + String sep = ""; + for (AnnotationValue av : annotationTypeValues) { + annotation.addContent(sep); + annotation.addContent(annotationValueToContent(av)); + sep = " "; + } + } + // If the container has 1 or more value defined and if the + // repeatable type annotation is not documented, print the container. + else { + addAnnotations(annotationElement, linkInfo, annotation, pairs, + indent, false); + } + } + else { + addAnnotations(annotationElement, linkInfo, annotation, pairs, + indent, linkBreak); + } + annotation.addContent(linkBreak ? DocletConstants.NL : ""); + results.add(annotation); + } + return results; + } + + /** + * Add annotation to the annotation string. + * + * @param annotationDoc the annotation being documented + * @param linkInfo the information about the link + * @param annotation the annotation string to which the annotation will be added + * @param pairs annotation type element and value pairs + * @param indent the number of extra spaces to indent the annotations. + * @param linkBreak if true, add new line between each member value + */ + private void addAnnotations(TypeElement annotationDoc, LinkInfoImpl linkInfo, + ContentBuilder annotation, Mapmap, + int indent, boolean linkBreak) { + linkInfo.label = new StringContent("@" + annotationDoc.getSimpleName().toString()); + annotation.addContent(getLink(linkInfo)); + if (!map.isEmpty()) { + annotation.addContent("("); + boolean isFirst = true; + for (ExecutableElement element : map.keySet()) { + if (isFirst) { + isFirst = false; + } else { + annotation.addContent(","); + if (linkBreak) { + annotation.addContent(DocletConstants.NL); + int spaces = annotationDoc.getSimpleName().toString().length() + 2; + for (int k = 0; k < (spaces + indent); k++) { + annotation.addContent(" "); + } + } + } + annotation.addContent(getDocLink(LinkInfoImpl.Kind.ANNOTATION, + element, element.getSimpleName().toString(), false)); + annotation.addContent("="); + AnnotationValue annotationValue = map.get(element); + List annotationTypeValues = new ArrayList<>(); + new SimpleAnnotationValueVisitor9() { + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Void visitArray(List vals, AnnotationValue p) { + annotationTypeValues.addAll(vals); + return null; + } + @Override @DefinedBy(Api.LANGUAGE_MODEL) + protected Void defaultAction(Object o, AnnotationValue p) { + annotationTypeValues.add(p); + return null; + } + }.visit(annotationValue, annotationValue); + annotation.addContent(annotationTypeValues.size() == 1 ? "" : "{"); + String sep = ""; + for (AnnotationValue av : annotationTypeValues) { + annotation.addContent(sep); + annotation.addContent(annotationValueToContent(av)); + sep = ","; + } + annotation.addContent(annotationTypeValues.size() == 1 ? "" : "}"); + isContainerDocumented = false; + } + annotation.addContent(")"); + } + } + + /** + * Check if the annotation contains an array of annotation as a value. This + * check is to verify if a repeatable type annotation is present or not. + * + * @param pairs annotation type element and value pairs + * + * @return true if the annotation contains an array of annotation as a value. + */ + private boolean isAnnotationArray(Map pairs) { + AnnotationValue annotationValue; + for (ExecutableElement ee : pairs.keySet()) { + annotationValue = pairs.get(ee); + boolean rvalue = new SimpleAnnotationValueVisitor9() { + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Boolean visitArray(List vals, Void p) { + if (vals.size() > 1) { + if (vals.get(0) instanceof AnnotationMirror) { + isContainerDocumented = true; + return new SimpleAnnotationValueVisitor9() { + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Boolean visitAnnotation(AnnotationMirror a, Void p) { + isContainerDocumented = true; + Element asElement = a.getAnnotationType().asElement(); + if (utils.isDocumentedAnnotation((TypeElement)asElement)) { + isAnnotationDocumented = true; + } + return true; + } + @Override @DefinedBy(Api.LANGUAGE_MODEL) + protected Boolean defaultAction(Object o, Void p) { + return false; + } + }.visit(vals.get(0)); + } + } + return false; + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + protected Boolean defaultAction(Object o, Void p) { + return false; + } + }.visit(annotationValue); + if (rvalue) { + return true; + } + } + return false; + } + + private Content annotationValueToContent(AnnotationValue annotationValue) { + return new SimpleAnnotationValueVisitor9() { + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Content visitType(TypeMirror t, Void p) { + return new SimpleTypeVisitor9() { + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Content visitDeclared(DeclaredType t, Void p) { + LinkInfoImpl linkInfo = new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.ANNOTATION, t); + String name = utils.isIncluded(t.asElement()) + ? t.asElement().getSimpleName().toString() + : utils.getFullyQualifiedName(t.asElement()); + linkInfo.label = new StringContent(name + utils.getDimension(t) + ".class"); + return getLink(linkInfo); + } + @Override @DefinedBy(Api.LANGUAGE_MODEL) + protected Content defaultAction(TypeMirror e, Void p) { + return new StringContent(t + utils.getDimension(t) + ".class"); + } + }.visit(t); + } + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Content visitAnnotation(AnnotationMirror a, Void p) { + List list = getAnnotations(0, a, false); + ContentBuilder buf = new ContentBuilder(); + for (Content c : list) { + buf.addContent(c); + } + return buf; + } + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Content visitEnumConstant(VariableElement c, Void p) { + return getDocLink(LinkInfoImpl.Kind.ANNOTATION, + c, c.getSimpleName().toString(), false); + } + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Content visitArray(List vals, Void p) { + ContentBuilder buf = new ContentBuilder(); + String sep = ""; + for (AnnotationValue av : vals) { + buf.addContent(sep); + buf.addContent(visit(av)); + sep = " "; + } + return buf; + } + @Override @DefinedBy(Api.LANGUAGE_MODEL) + protected Content defaultAction(Object o, Void p) { + return new StringContent(annotationValue.toString()); + } + }.visit(annotationValue); + } + + /** + * Return the configuration for this doclet. + * + * @return the configuration for this doclet. + */ + public Configuration configuration() { + return configuration; + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlSerialFieldWriter.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlSerialFieldWriter.java new file mode 100644 index 00000000000..5421c278411 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlSerialFieldWriter.java @@ -0,0 +1,221 @@ +/* + * Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.util.*; + +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; + +import com.sun.source.doctree.DocTree; + +import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.RawHtml; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.SerializedFormWriter; +import jdk.javadoc.internal.doclets.toolkit.taglets.TagletWriter; +import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper; + +/** + * Generate serialized form for serializable fields. + * Documentation denoted by the tags serial and + * serialField is processed. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Joe Fialli + * @author Bhavesh Patel (Modified) + */ +public class HtmlSerialFieldWriter extends FieldWriterImpl + implements SerializedFormWriter.SerialFieldWriter { + + public HtmlSerialFieldWriter(SubWriterHolderWriter writer, TypeElement typeElement) { + super(writer, typeElement); + } + + public SortedSet members(TypeElement te) { + return utils.serializableFields(te); + } + + /** + * Return the header for serializable fields section. + * + * @return a content tree for the header + */ + public Content getSerializableFieldsHeader() { + HtmlTree ul = new HtmlTree(HtmlTag.UL); + ul.addStyle(HtmlStyle.blockList); + return ul; + } + + /** + * Return the header for serializable fields content section. + * + * @param isLastContent true if the cotent being documented is the last content. + * @return a content tree for the header + */ + public Content getFieldsContentHeader(boolean isLastContent) { + HtmlTree li = new HtmlTree(HtmlTag.LI); + if (isLastContent) + li.addStyle(HtmlStyle.blockListLast); + else + li.addStyle(HtmlStyle.blockList); + return li; + } + + /** + * Add serializable fields. + * + * @param heading the heading for the section + * @param serializableFieldsTree the tree to be added to the serializable fileds + * content tree + * @return a content tree for the serializable fields content + */ + public Content getSerializableFields(String heading, Content serializableFieldsTree) { + HtmlTree li = new HtmlTree(HtmlTag.LI); + li.addStyle(HtmlStyle.blockList); + if (serializableFieldsTree.isValid()) { + Content headingContent = new StringContent(heading); + Content serialHeading = HtmlTree.HEADING(HtmlConstants.SERIALIZED_MEMBER_HEADING, + headingContent); + li.addContent(serialHeading); + li.addContent(serializableFieldsTree); + } + return li; + } + + /** + * Add the member header. + * + * @param fieldType the class document to be listed + * @param fieldTypeStr the string for the field type to be documented + * @param fieldDimensions the dimensions of the field string to be added + * @param fieldName name of the field to be added + * @param contentTree the content tree to which the member header will be added + */ + public void addMemberHeader(TypeElement fieldType, String fieldTypeStr, + String fieldDimensions, String fieldName, Content contentTree) { + Content nameContent = new RawHtml(fieldName); + Content heading = HtmlTree.HEADING(HtmlConstants.MEMBER_HEADING, nameContent); + contentTree.addContent(heading); + Content pre = new HtmlTree(HtmlTag.PRE); + if (fieldType == null) { + pre.addContent(fieldTypeStr); + } else { + Content fieldContent = writer.getLink(new LinkInfoImpl( + configuration, LinkInfoImpl.Kind.SERIAL_MEMBER, fieldType)); + pre.addContent(fieldContent); + } + pre.addContent(fieldDimensions + " "); + pre.addContent(fieldName); + contentTree.addContent(pre); + } + + /** + * Add the deprecated information for this member. + * + * @param field the field to document. + * @param contentTree the tree to which the deprecated info will be added + */ + public void addMemberDeprecatedInfo(VariableElement field, Content contentTree) { + addDeprecatedInfo(field, contentTree); + } + + /** + * Add the description text for this member. + * + * @param field the field to document. + * @param contentTree the tree to which the deprecated info will be added + */ + public void addMemberDescription(VariableElement field, Content contentTree) { + if (!utils.getBody(field).isEmpty()) { + writer.addInlineComment(field, contentTree); + } + List tags = utils.getBlockTags(field, DocTree.Kind.SERIAL); + if (!tags.isEmpty()) { + writer.addInlineComment(field, tags.get(0), contentTree); + } + } + + /** + * Add the description text for this member represented by the tag. + * + * @param serialFieldTag the field to document (represented by tag) + * @param contentTree the tree to which the deprecated info will be added + */ + public void addMemberDescription(VariableElement field, DocTree serialFieldTag, Content contentTree) { + CommentHelper ch = utils.getCommentHelper(field); + List description = ch.getDescription(configuration, serialFieldTag); + if (!description.isEmpty()) { + Content serialFieldContent = new RawHtml(ch.getText(description)); + Content div = HtmlTree.DIV(HtmlStyle.block, serialFieldContent); + contentTree.addContent(div); + } + } + + /** + * Add the tag information for this member. + * + * @param field the field to document. + * @param contentTree the tree to which the member tags info will be added + */ + public void addMemberTags(VariableElement field, Content contentTree) { + Content tagContent = new ContentBuilder(); + TagletWriter.genTagOutput(configuration.tagletManager, field, + configuration.tagletManager.getCustomTaglets(field), + writer.getTagletWriterInstance(false), tagContent); + Content dlTags = new HtmlTree(HtmlTag.DL); + dlTags.addContent(tagContent); + contentTree.addContent(dlTags); // TODO: what if empty? + } + + /** + * Check to see if overview details should be printed. If + * nocomment option set or if there is no text to be printed + * for deprecation info, comment or tags, do not print overview details. + * + * @param field the field to check overview details for. + * @return true if overview details need to be printed + */ + public boolean shouldPrintOverview(VariableElement field) { + if (!configuration.nocomment) { + if(!utils.getBody(field).isEmpty() || + writer.hasSerializationOverviewTags(field)) + return true; + } + if (utils.isDeprecated(field)) + return true; + return false; + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlSerialMethodWriter.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlSerialMethodWriter.java new file mode 100644 index 00000000000..ca58a2e4dea --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlSerialMethodWriter.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; + +import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.SerializedFormWriter; +import jdk.javadoc.internal.doclets.toolkit.taglets.TagletManager; +import jdk.javadoc.internal.doclets.toolkit.taglets.TagletWriter; + + +/** + * Generate serialized form for Serializable/Externalizable methods. + * Documentation denoted by the serialData tag is processed. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Joe Fialli + * @author Bhavesh Patel (Modified) + */ +public class HtmlSerialMethodWriter extends MethodWriterImpl implements + SerializedFormWriter.SerialMethodWriter{ + + public HtmlSerialMethodWriter(SubWriterHolderWriter writer, TypeElement typeElement) { + super(writer, typeElement); + } + + /** + * Return the header for serializable methods section. + * + * @return a content tree for the header + */ + public Content getSerializableMethodsHeader() { + HtmlTree ul = new HtmlTree(HtmlTag.UL); + ul.addStyle(HtmlStyle.blockList); + return ul; + } + + /** + * Return the header for serializable methods content section. + * + * @param isLastContent true if the cotent being documented is the last content. + * @return a content tree for the header + */ + public Content getMethodsContentHeader(boolean isLastContent) { + HtmlTree li = new HtmlTree(HtmlTag.LI); + if (isLastContent) + li.addStyle(HtmlStyle.blockListLast); + else + li.addStyle(HtmlStyle.blockList); + return li; + } + + /** + * Add serializable methods. + * + * @param heading the heading for the section + * @param serializableMethodContent the tree to be added to the serializable methods + * content tree + * @return a content tree for the serializable methods content + */ + public Content getSerializableMethods(String heading, Content serializableMethodContent) { + Content headingContent = new StringContent(heading); + Content serialHeading = HtmlTree.HEADING(HtmlConstants.SERIALIZED_MEMBER_HEADING, + headingContent); + Content li = HtmlTree.LI(HtmlStyle.blockList, serialHeading); + li.addContent(serializableMethodContent); + return li; + } + + /** + * Return the no customization message. + * + * @param msg the message to be displayed + * @return no customization message content + */ + public Content getNoCustomizationMsg(String msg) { + Content noCustomizationMsg = new StringContent(msg); + return noCustomizationMsg; + } + + /** + * Add the member header. + * + * @param member the method document to be listed + * @param methodsContentTree the content tree to which the member header will be added + */ + public void addMemberHeader(ExecutableElement member, Content methodsContentTree) { + methodsContentTree.addContent(getHead(member)); + methodsContentTree.addContent(getSignature(member)); + } + + /** + * Add the deprecated information for this member. + * + * @param member the method to document. + * @param methodsContentTree the tree to which the deprecated info will be added + */ + public void addDeprecatedMemberInfo(ExecutableElement member, Content methodsContentTree) { + addDeprecatedInfo(member, methodsContentTree); + } + + /** + * Add the description text for this member. + * + * @param member the method to document. + * @param methodsContentTree the tree to which the deprecated info will be added + */ + public void addMemberDescription(ExecutableElement member, Content methodsContentTree) { + addComment(member, methodsContentTree); + } + + /** + * Add the tag information for this member. + * + * @param member the method to document. + * @param methodsContentTree the tree to which the member tags info will be added + */ + public void addMemberTags(ExecutableElement member, Content methodsContentTree) { + Content tagContent = new ContentBuilder(); + TagletManager tagletManager = + configuration.tagletManager; + TagletWriter.genTagOutput(tagletManager, member, + tagletManager.getSerializedFormTaglets(), + writer.getTagletWriterInstance(false), tagContent); + Content dlTags = new HtmlTree(HtmlTag.DL); + dlTags.addContent(tagContent); + methodsContentTree.addContent(dlTags); + if (name(member).compareTo("writeExternal") == 0 + && utils.getSerialDataTrees(member).isEmpty()) { + serialWarning(member, "doclet.MissingSerialDataTag", + utils.getFullyQualifiedName(member.getEnclosingElement()), name(member)); + } + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/LinkFactoryImpl.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/LinkFactoryImpl.java new file mode 100644 index 00000000000..dbb476920be --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/LinkFactoryImpl.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + +import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; +import jdk.javadoc.internal.doclets.toolkit.Configuration; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.Utils; +import jdk.javadoc.internal.doclets.toolkit.util.links.LinkFactory; +import jdk.javadoc.internal.doclets.toolkit.util.links.LinkInfo; + +import static jdk.javadoc.internal.doclets.formats.html.LinkInfoImpl.Kind.MEMBER_TYPE_PARAMS; + +/** + * A factory that returns a link given the information about it. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Jamie Ho + */ +public class LinkFactoryImpl extends LinkFactory { + + private final HtmlDocletWriter m_writer; + + public LinkFactoryImpl(HtmlDocletWriter writer) { + m_writer = writer; + } + + /** + * {@inheritDoc} + */ + @Override + protected Content newContent() { + return new ContentBuilder(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Content getClassLink(LinkInfo linkInfo) { + Configuration configuration = m_writer.configuration; + Utils utils = configuration.utils; + LinkInfoImpl classLinkInfo = (LinkInfoImpl) linkInfo; + boolean noLabel = linkInfo.label == null || linkInfo.label.isEmpty(); + TypeElement typeElement = classLinkInfo.typeElement; + // Create a tool tip if we are linking to a class or interface. Don't + // create one if we are linking to a member. + String title = ""; + if (classLinkInfo.where == null || classLinkInfo.where.length() == 0) { + boolean isTypeLink = classLinkInfo.type != null && + utils.isTypeVariable(utils.getComponentType(classLinkInfo.type)); + title = getClassToolTip(typeElement, isTypeLink); + } + Content label = classLinkInfo.getClassLinkLabel(m_writer.configuration); + + Content link = new ContentBuilder(); + if (utils.isIncluded(typeElement)) { + if (configuration.isGeneratedDoc(typeElement)) { + DocPath filename = getPath(classLinkInfo); + if (linkInfo.linkToSelf || + !(DocPath.forName(utils, typeElement)).equals(m_writer.filename)) { + link.addContent(m_writer.getHyperLink( + filename.fragment(classLinkInfo.where), + label, + classLinkInfo.isStrong, classLinkInfo.styleName, + title, classLinkInfo.target)); + if (noLabel && !classLinkInfo.excludeTypeParameterLinks) { + link.addContent(getTypeParameterLinks(linkInfo)); + } + return link; + } + } + } else { + Content crossLink = m_writer.getCrossClassLink( + typeElement.getQualifiedName().toString(), classLinkInfo.where, + label, classLinkInfo.isStrong, classLinkInfo.styleName, + true); + if (crossLink != null) { + link.addContent(crossLink); + if (noLabel && !classLinkInfo.excludeTypeParameterLinks) { + link.addContent(getTypeParameterLinks(linkInfo)); + } + return link; + } + } + // Can't link so just write label. + link.addContent(label); + if (noLabel && !classLinkInfo.excludeTypeParameterLinks) { + link.addContent(getTypeParameterLinks(linkInfo)); + } + return link; + } + + /** + * {@inheritDoc} + */ + @Override + protected Content getTypeParameterLink(LinkInfo linkInfo, TypeMirror typeParam) { + LinkInfoImpl typeLinkInfo = new LinkInfoImpl(m_writer.configuration, + ((LinkInfoImpl) linkInfo).getContext(), typeParam); + typeLinkInfo.excludeTypeBounds = linkInfo.excludeTypeBounds; + typeLinkInfo.excludeTypeParameterLinks = linkInfo.excludeTypeParameterLinks; + typeLinkInfo.linkToSelf = linkInfo.linkToSelf; + typeLinkInfo.isJava5DeclarationLocation = false; + return getLink(typeLinkInfo); + } + + @Override + protected Content getTypeAnnotationLink(LinkInfo linkInfo, AnnotationMirror annotation) { + throw new RuntimeException("Not implemented yet!"); + } + + @Override + public Content getTypeAnnotationLinks(LinkInfo linkInfo) { + Utils utils = ((LinkInfoImpl)linkInfo).utils; + ContentBuilder links = new ContentBuilder(); + List annotations; + if (utils.isAnnotated(linkInfo.type)) { + annotations = linkInfo.type.getAnnotationMirrors(); + } else if (utils.isTypeVariable(linkInfo.type)) { + // TODO: use the context for now, and special case for Receiver_Types, + // which takes the default case. + switch (((LinkInfoImpl)linkInfo).context) { + case MEMBER_TYPE_PARAMS: + case EXECUTABLE_MEMBER_PARAM: + case CLASS_SIGNATURE: + Element element = utils.typeUtils.asElement(linkInfo.type); + annotations = element.getAnnotationMirrors(); + break; + default: + annotations = linkInfo.type.getAnnotationMirrors(); + break; + } + + } else { + return links; + } + + if (annotations.isEmpty()) + return links; + + List annos = m_writer.getAnnotations(0, annotations, false, linkInfo.isJava5DeclarationLocation); + + boolean isFirst = true; + for (Content anno : annos) { + if (!isFirst) { + links.addContent(" "); + } + links.addContent(anno); + isFirst = false; + } + if (!annos.isEmpty()) { + links.addContent(" "); + } + + return links; + } + + /** + * Given a class, return the appropriate tool tip. + * + * @param typeElement the class to get the tool tip for. + * @return the tool tip for the appropriate class. + */ + private String getClassToolTip(TypeElement typeElement, boolean isTypeLink) { + Configuration configuration = m_writer.configuration; + Utils utils = configuration.utils; + if (isTypeLink) { + return configuration.getText("doclet.Href_Type_Param_Title", + utils.getSimpleName(typeElement)); + } else if (utils.isInterface(typeElement)){ + return configuration.getText("doclet.Href_Interface_Title", + utils.getPackageName(utils.containingPackage(typeElement))); + } else if (utils.isAnnotationType(typeElement)) { + return configuration.getText("doclet.Href_Annotation_Title", + utils.getPackageName(utils.containingPackage(typeElement))); + } else if (utils.isEnum(typeElement)) { + return configuration.getText("doclet.Href_Enum_Title", + utils.getPackageName(utils.containingPackage(typeElement))); + } else { + return configuration.getText("doclet.Href_Class_Title", + utils.getPackageName(utils.containingPackage(typeElement))); + } + } + + /** + * Return path to the given file name in the given package. So if the name + * passed is "Object.html" and the name of the package is "java.lang", and + * if the relative path is "../.." then returned string will be + * "../../java/lang/Object.html" + * + * @param linkInfo the information about the link. + */ + private DocPath getPath(LinkInfoImpl linkInfo) { + if (linkInfo.context == LinkInfoImpl.Kind.PACKAGE_FRAME) { + //Not really necessary to do this but we want to be consistent + //with 1.4.2 output. + return DocPath.forName(linkInfo.utils, linkInfo.typeElement); + } + return m_writer.pathToRoot.resolve(DocPath.forClass(linkInfo.utils, linkInfo.typeElement)); + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/LinkInfoImpl.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/LinkInfoImpl.java new file mode 100644 index 00000000000..01cd9508a1a --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/LinkInfoImpl.java @@ -0,0 +1,450 @@ +/* + * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + +import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.Utils; +import jdk.javadoc.internal.doclets.toolkit.util.links.LinkInfo; + + +/** + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class LinkInfoImpl extends LinkInfo { + + public enum Kind { + DEFAULT, + + /** + * Indicate that the link appears in a class list. + */ + ALL_CLASSES_FRAME, + + /** + * Indicate that the link appears in a class documentation. + */ + CLASS, + + /** + * Indicate that the link appears in member documentation. + */ + MEMBER, + + /** + * Indicate that the link appears in class use documentation. + */ + CLASS_USE, + + /** + * Indicate that the link appears in index documentation. + */ + INDEX, + + /** + * Indicate that the link appears in constant value summary. + */ + CONSTANT_SUMMARY, + + /** + * Indicate that the link appears in serialized form documentation. + */ + SERIALIZED_FORM, + + /** + * Indicate that the link appears in serial member documentation. + */ + SERIAL_MEMBER, + + /** + * Indicate that the link appears in package documentation. + */ + PACKAGE, + + /** + * Indicate that the link appears in see tag documentation. + */ + SEE_TAG, + + /** + * Indicate that the link appears in value tag documentation. + */ + VALUE_TAG, + + /** + * Indicate that the link appears in tree documentation. + */ + TREE, + + /** + * Indicate that the link appears in a class list. + */ + PACKAGE_FRAME, + + /** + * The header in the class documentation. + */ + CLASS_HEADER, + + /** + * The signature in the class documentation. + */ + CLASS_SIGNATURE, + + /** + * The return type of a method. + */ + RETURN_TYPE, + + /** + * The return type of a method in a member summary. + */ + SUMMARY_RETURN_TYPE, + + /** + * The type of a method/constructor parameter. + */ + EXECUTABLE_MEMBER_PARAM, + + /** + * Super interface links. + */ + SUPER_INTERFACES, + + /** + * Implemented interface links. + */ + IMPLEMENTED_INTERFACES, + + /** + * Implemented class links. + */ + IMPLEMENTED_CLASSES, + + /** + * Subinterface links. + */ + SUBINTERFACES, + + /** + * Subclasses links. + */ + SUBCLASSES, + + /** + * The signature in the class documentation (implements/extends portion). + */ + CLASS_SIGNATURE_PARENT_NAME, + + /** + * The header for method documentation copied from parent. + */ + EXECUTABLE_ELEMENT_COPY, + + /** + * Method "specified by" link. + */ + METHOD_SPECIFIED_BY, + + /** + * Method "overrides" link. + */ + METHOD_OVERRIDES, + + /** + * Annotation link. + */ + ANNOTATION, + + /** + * The header for field documentation copied from parent. + */ + VARIABLE_ELEMENT_COPY, + + /** + * The parent nodes in the class tree. + */ + CLASS_TREE_PARENT, + + /** + * The type parameters of a method or constructor. + */ + MEMBER_TYPE_PARAMS, + + /** + * Indicate that the link appears in class use documentation. + */ + CLASS_USE_HEADER, + + /** + * The header for property documentation copied from parent. + */ + PROPERTY_COPY, + + /** + * A receiver type + */ + RECEIVER_TYPE + } + + public final ConfigurationImpl configuration; + + /** + * The location of the link. + */ + public Kind context = Kind.DEFAULT; + + /** + * The value of the marker #. + */ + public String where = ""; + + /** + * String style of text defined in style sheet. + */ + public String styleName = ""; + + /** + * The value of the target. + */ + public String target = ""; + public final Utils utils; + /** + * Construct a LinkInfo object. + * + * @param configuration the configuration data for the doclet + * @param context the context of the link. + * @param ee the member to link to. + */ + public LinkInfoImpl(ConfigurationImpl configuration, Kind context, ExecutableElement ee) { + this.configuration = configuration; + this.utils = configuration.utils; + this.executableElement = ee; + setContext(context); + } + + /** + * {@inheritDoc} + */ + @Override + protected Content newContent() { + return new ContentBuilder(); + } + + /** + * Construct a LinkInfo object. + * + * @param configuration the configuration data for the doclet + * @param context the context of the link. + * @param typeElement the class to link to. + */ + public LinkInfoImpl(ConfigurationImpl configuration, Kind context, TypeElement typeElement) { + this.configuration = configuration; + this.utils = configuration.utils; + this.typeElement = typeElement; + setContext(context); + } + + /** + * Construct a LinkInfo object. + * + * @param configuration the configuration data for the doclet + * @param context the context of the link. + * @param type the class to link to. + */ + public LinkInfoImpl(ConfigurationImpl configuration, Kind context, TypeMirror type) { + this.configuration = configuration; + this.utils = configuration.utils; + this.type = type; + setContext(context); + } + + /** + * Set the label for the link. + * @param label plain-text label for the link + */ + public LinkInfoImpl label(String label) { + this.label = new StringContent(label); + return this; + } + + /** + * Set the label for the link. + */ + public LinkInfoImpl label(Content label) { + this.label = label; + return this; + } + + /** + * Set whether or not the link should be strong. + */ + public LinkInfoImpl strong(boolean strong) { + this.isStrong = strong; + return this; + } + + /** + * Set the style to be used for the link. + * @param styleName String style of text defined in style sheet. + */ + public LinkInfoImpl styleName(String styleName) { + this.styleName = styleName; + return this; + } + + /** + * Set the target to be used for the link. + * @param styleName String style of text defined in style sheet. + */ + public LinkInfoImpl target(String target) { + this.target = target; + return this; + } + + /** + * Set whether or not this is a link to a varargs parameter. + */ + public LinkInfoImpl varargs(boolean varargs) { + this.isVarArg = varargs; + return this; + } + + /** + * Set the fragment specifier for the link. + */ + public LinkInfoImpl where(String where) { + this.where = where; + return this; + } + + /** + * {@inheritDoc} + */ + public Kind getContext() { + return context; + } + + /** + * {@inheritDoc} + * + * This method sets the link attributes to the appropriate values + * based on the context. + * + * @param c the context id to set. + */ + public final void setContext(Kind c) { + //NOTE: Put context specific link code here. + switch (c) { + case ALL_CLASSES_FRAME: + case PACKAGE_FRAME: + case IMPLEMENTED_CLASSES: + case SUBCLASSES: + case EXECUTABLE_ELEMENT_COPY: + case VARIABLE_ELEMENT_COPY: + case PROPERTY_COPY: + case CLASS_USE_HEADER: + includeTypeInClassLinkLabel = false; + break; + + case ANNOTATION: + excludeTypeParameterLinks = true; + excludeTypeBounds = true; + break; + + case IMPLEMENTED_INTERFACES: + case SUPER_INTERFACES: + case SUBINTERFACES: + case CLASS_TREE_PARENT: + case TREE: + case CLASS_SIGNATURE_PARENT_NAME: + excludeTypeParameterLinks = true; + excludeTypeBounds = true; + includeTypeInClassLinkLabel = false; + includeTypeAsSepLink = true; + break; + + case PACKAGE: + case CLASS_USE: + case CLASS_HEADER: + case CLASS_SIGNATURE: + case RECEIVER_TYPE: + excludeTypeParameterLinks = true; + includeTypeAsSepLink = true; + includeTypeInClassLinkLabel = false; + break; + + case MEMBER_TYPE_PARAMS: + includeTypeAsSepLink = true; + includeTypeInClassLinkLabel = false; + break; + + case RETURN_TYPE: + case SUMMARY_RETURN_TYPE: + excludeTypeBounds = true; + break; + case EXECUTABLE_MEMBER_PARAM: + excludeTypeBounds = true; + break; + } + context = c; + if (type != null && + utils.isTypeVariable(type) && + utils.isExecutableElement(utils.asTypeElement(type).getEnclosingElement())) { + excludeTypeParameterLinks = true; + } + } + + /** + * Return true if this link is linkable and false if we can't link to the + * desired place. + * + * @return true if this link is linkable and false if we can't link to the + * desired place. + */ + @Override + public boolean isLinkable() { + return configuration.utils.isLinkable(typeElement); + } + + @Override + public String toString() { + return "LinkInfoImpl{" + + "context=" + context + + ", where=" + where + + ", styleName=" + styleName + + ", target=" + target + + super.toString() + '}'; + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/LinkOutputImpl.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/LinkOutputImpl.java new file mode 100644 index 00000000000..f13d7e9a282 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/LinkOutputImpl.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import jdk.javadoc.internal.doclets.toolkit.util.links.LinkOutput; + +/** + * Stores output of a link. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Jamie Ho + */ +public class LinkOutputImpl implements LinkOutput { + + /** + * The output of the link. + */ + public StringBuilder output; + + /** + * Construct a new LinkOutputImpl. + */ + public LinkOutputImpl() { + output = new StringBuilder(); + } + + /** + * {@inheritDoc} + */ + public void append(Object o) { + output.append(o instanceof String ? + (String) o : ((LinkOutputImpl)o).toString()); + } + + /** + * {@inheritDoc} + */ + public void insert(int offset, Object o) { + output.insert(offset, o.toString()); + } + + /** + * {@inheritDoc} + */ + public String toString() { + return output.toString(); + } + +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/MethodWriterImpl.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/MethodWriterImpl.java new file mode 100644 index 00000000000..40b00cdd1fd --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/MethodWriterImpl.java @@ -0,0 +1,457 @@ +/* + * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; + +import java.util.Arrays; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.MemberSummaryWriter; +import jdk.javadoc.internal.doclets.toolkit.MethodWriter; +import jdk.javadoc.internal.doclets.toolkit.util.ImplementedMethods; +import jdk.javadoc.internal.doclets.toolkit.util.Utils; + +/** + * Writes method documentation in HTML format. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Robert Field + * @author Atul M Dambalkar + * @author Jamie Ho (rewrite) + * @author Bhavesh Patel (Modified) + */ +public class MethodWriterImpl extends AbstractExecutableMemberWriter + implements MethodWriter, MemberSummaryWriter { + + /** + * Construct a new MethodWriterImpl. + * + * @param writer the writer for the class that the methods belong to. + * @param typeElement the class being documented. + */ + public MethodWriterImpl(SubWriterHolderWriter writer, TypeElement typeElement) { + super(writer, typeElement); + } + + /** + * Construct a new MethodWriterImpl. + * + * @param writer The writer for the class that the methods belong to. + */ + public MethodWriterImpl(SubWriterHolderWriter writer) { + super(writer); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getMemberSummaryHeader(TypeElement typeElement, Content memberSummaryTree) { + memberSummaryTree.addContent(HtmlConstants.START_OF_METHOD_SUMMARY); + Content memberTree = writer.getMemberTreeHeader(); + writer.addSummaryHeader(this, typeElement, memberTree); + return memberTree; + } + + /** + * {@inheritDoc} + */ + public void addMemberTree(Content memberSummaryTree, Content memberTree) { + writer.addMemberTree(memberSummaryTree, memberTree); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getMethodDetailsTreeHeader(TypeElement typeElement, Content memberDetailsTree) { + memberDetailsTree.addContent(HtmlConstants.START_OF_METHOD_DETAILS); + Content methodDetailsTree = writer.getMemberTreeHeader(); + methodDetailsTree.addContent(writer.getMarkerAnchor( + SectionName.METHOD_DETAIL)); + Content heading = HtmlTree.HEADING(HtmlConstants.DETAILS_HEADING, + writer.methodDetailsLabel); + methodDetailsTree.addContent(heading); + return methodDetailsTree; + } + + /** + * {@inheritDoc} + */ + @Override + public Content getMethodDocTreeHeader(ExecutableElement method, Content methodDetailsTree) { + String erasureAnchor; + if ((erasureAnchor = getErasureAnchor(method)) != null) { + methodDetailsTree.addContent(writer.getMarkerAnchor((erasureAnchor))); + } + methodDetailsTree.addContent( + writer.getMarkerAnchor(writer.getAnchor(method))); + Content methodDocTree = writer.getMemberTreeHeader(); + Content heading = new HtmlTree(HtmlConstants.MEMBER_HEADING); + heading.addContent(name(method)); + methodDocTree.addContent(heading); + return methodDocTree; + } + + /** + * Get the signature for the given method. + * + * @param method the method being documented. + * @return a content object for the signature + */ + @Override + public Content getSignature(ExecutableElement method) { + Content pre = new HtmlTree(HtmlTag.PRE); + writer.addAnnotationInfo(method, pre); + int annotationLength = pre.charCount(); + addModifiers(method, pre); + addTypeParameters(method, pre); + addReturnType(method, pre); + if (configuration.linksource) { + Content methodName = new StringContent(name(method)); + writer.addSrcLink(method, methodName, pre); + } else { + addName(name(method), pre); + } + int indent = pre.charCount() - annotationLength; + addParameters(method, pre, indent); + addExceptions(method, pre, indent); + return pre; + } + + /** + * {@inheritDoc} + */ + @Override + public void addDeprecated(ExecutableElement method, Content methodDocTree) { + addDeprecatedInfo(method, methodDocTree); + } + + /** + * {@inheritDoc} + */ + @Override + public void addComments(TypeMirror holderType, ExecutableElement method, Content methodDocTree) { + TypeElement holder = utils.asTypeElement(holderType); + if (!utils.getBody(method).isEmpty()) { + if (holder.equals(typeElement) || + !(utils.isPublic(holder) || + utils.isLinkable(holder))) { + writer.addInlineComment(method, methodDocTree); + } else { + Content link = + writer.getDocLink(LinkInfoImpl.Kind.EXECUTABLE_ELEMENT_COPY, + holder, method, + utils.isIncluded(holder) + ? utils.getSimpleName(holder) + : utils.getFullyQualifiedName(holder), + false); + Content codelLink = HtmlTree.CODE(link); + Content descfrmLabel = HtmlTree.SPAN(HtmlStyle.descfrmTypeLabel, + utils.isClass(holder) + ? writer.descfrmClassLabel + : writer.descfrmInterfaceLabel); + descfrmLabel.addContent(writer.getSpace()); + descfrmLabel.addContent(codelLink); + methodDocTree.addContent(HtmlTree.DIV(HtmlStyle.block, descfrmLabel)); + writer.addInlineComment(method, methodDocTree); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void addTags(ExecutableElement method, Content methodDocTree) { + writer.addTagsInfo(method, methodDocTree); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getMethodDetails(Content methodDetailsTree) { + if (configuration.allowTag(HtmlTag.SECTION)) { + HtmlTree htmlTree = HtmlTree.SECTION(getMemberTree(methodDetailsTree)); + return htmlTree; + } + return getMemberTree(methodDetailsTree); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getMethodDoc(Content methodDocTree, + boolean isLastContent) { + return getMemberTree(methodDocTree, isLastContent); + } + + /** + * Close the writer. + */ + @Override + public void close() throws IOException { + writer.close(); + } + + /** + * {@inheritDoc} + */ + @Override + public void addSummaryLabel(Content memberTree) { + Content label = HtmlTree.HEADING(HtmlConstants.SUMMARY_HEADING, + writer.getResource("doclet.Method_Summary")); + memberTree.addContent(label); + } + + /** + * {@inheritDoc} + */ + @Override + public String getTableSummary() { + return configuration.getText("doclet.Member_Table_Summary", + configuration.getText("doclet.Method_Summary"), + configuration.getText("doclet.methods")); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getCaption() { + return configuration.getResource("doclet.Methods"); + } + + /** + * {@inheritDoc} + */ + @Override + public List getSummaryTableHeader(Element member) { + List header = Arrays.asList(writer.getModifierTypeHeader(), + configuration.getText("doclet.0_and_1", + configuration.getText("doclet.Method"), + configuration.getText("doclet.Description"))); + return header; + } + + /** + * {@inheritDoc} + */ + @Override + public void addSummaryAnchor(TypeElement typeElement, Content memberTree) { + memberTree.addContent(writer.getMarkerAnchor( + SectionName.METHOD_SUMMARY)); + } + + /** + * {@inheritDoc} + */ + @Override + public void addInheritedSummaryAnchor(TypeElement typeElement, Content inheritedTree) { + inheritedTree.addContent(writer.getMarkerAnchor( + SectionName.METHODS_INHERITANCE, configuration.getClassName(typeElement))); + } + + /** + * {@inheritDoc} + */ + @Override + public void addInheritedSummaryLabel(TypeElement typeElement, Content inheritedTree) { + Content classLink = writer.getPreQualifiedClassLink( + LinkInfoImpl.Kind.MEMBER, typeElement, false); + Content label = new StringContent(utils.isClass(typeElement) + ? configuration.getText("doclet.Methods_Inherited_From_Class") + : configuration.getText("doclet.Methods_Inherited_From_Interface")); + Content labelHeading = HtmlTree.HEADING(HtmlConstants.INHERITED_SUMMARY_HEADING, + label); + labelHeading.addContent(writer.getSpace()); + labelHeading.addContent(classLink); + inheritedTree.addContent(labelHeading); + } + + /** + * {@inheritDoc} + */ + @Override + protected void addSummaryType(Element member, Content tdSummaryType) { + ExecutableElement meth = (ExecutableElement)member; + addModifierAndType(meth, utils.getReturnType(meth), tdSummaryType); + } + + /** + * {@inheritDoc} + */ + protected static void addOverridden(HtmlDocletWriter writer, + TypeMirror overriddenType, ExecutableElement method, Content dl) { + if (writer.configuration.nocomment) { + return; + } + Utils utils = writer.configuration().utils; + TypeElement holder = utils.getEnclosingTypeElement(method); + if (!(utils.isPublic(holder) || + utils.isLinkable(holder))) { + //This is an implementation detail that should not be documented. + return; + } + if (utils.isIncluded(holder) && ! utils.isIncluded(method)) { + //The class is included but the method is not. That means that it + //is not visible so don't document this. + return; + } + Content label = writer.overridesLabel; + LinkInfoImpl.Kind context = LinkInfoImpl.Kind.METHOD_OVERRIDES; + + if (method != null) { + if (utils.isAbstract(holder) && utils.isAbstract(method)){ + //Abstract method is implemented from abstract class, + //not overridden + label = writer.specifiedByLabel; + context = LinkInfoImpl.Kind.METHOD_SPECIFIED_BY; + } + Content dt = HtmlTree.DT(HtmlTree.SPAN(HtmlStyle.overrideSpecifyLabel, label)); + dl.addContent(dt); + Content overriddenTypeLink = + writer.getLink(new LinkInfoImpl(writer.configuration, context, overriddenType)); + Content codeOverridenTypeLink = HtmlTree.CODE(overriddenTypeLink); + String name = method.getSimpleName().toString(); + Content methlink = writer.getLink( + new LinkInfoImpl(writer.configuration, LinkInfoImpl.Kind.MEMBER, + holder) + .where(writer.getName(writer.getAnchor(method))).label(name)); + Content codeMethLink = HtmlTree.CODE(methlink); + Content dd = HtmlTree.DD(codeMethLink); + dd.addContent(writer.getSpace()); + dd.addContent(writer.getResource("doclet.in_class")); + dd.addContent(writer.getSpace()); + dd.addContent(codeOverridenTypeLink); + dl.addContent(dd); + } + } + + /** + * {@inheritDoc} + */ + protected static void addImplementsInfo(HtmlDocletWriter writer, + ExecutableElement method, Content dl) { + if (writer.configuration.nocomment) { + return; + } + Utils utils = writer.utils; + ImplementedMethods implementedMethodsFinder = + new ImplementedMethods(method, writer.configuration); + SortedSet implementedMethods = + new TreeSet<>(utils.makeOverrideUseComparator()); + implementedMethods.addAll(implementedMethodsFinder.build()); + for (ExecutableElement implementedMeth : implementedMethods) { + TypeMirror intfac = implementedMethodsFinder.getMethodHolder(implementedMeth); + intfac = utils.getDeclaredType(utils.getEnclosingTypeElement(method), intfac); + Content intfaclink = writer.getLink(new LinkInfoImpl( + writer.configuration, LinkInfoImpl.Kind.METHOD_SPECIFIED_BY, intfac)); + Content codeIntfacLink = HtmlTree.CODE(intfaclink); + Content dt = HtmlTree.DT(HtmlTree.SPAN(HtmlStyle.overrideSpecifyLabel, writer.specifiedByLabel)); + dl.addContent(dt); + Content methlink = writer.getDocLink( + LinkInfoImpl.Kind.MEMBER, implementedMeth, + implementedMeth.getSimpleName().toString(), false); + Content codeMethLink = HtmlTree.CODE(methlink); + Content dd = HtmlTree.DD(codeMethLink); + dd.addContent(writer.getSpace()); + dd.addContent(writer.getResource("doclet.in_interface")); + dd.addContent(writer.getSpace()); + dd.addContent(codeIntfacLink); + dl.addContent(dd); + } + } + + /** + * Add the return type. + * + * @param method the method being documented. + * @param htmltree the content tree to which the return type will be added + */ + protected void addReturnType(ExecutableElement method, Content htmltree) { + TypeMirror type = utils.getReturnType(method); + if (type != null) { + Content linkContent = writer.getLink( + new LinkInfoImpl(configuration, LinkInfoImpl.Kind.RETURN_TYPE, type)); + htmltree.addContent(linkContent); + htmltree.addContent(writer.getSpace()); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected Content getNavSummaryLink(TypeElement typeElement, boolean link) { + if (link) { + if (typeElement == null) { + return writer.getHyperLink( + SectionName.METHOD_SUMMARY, + writer.getResource("doclet.navMethod")); + } else { + return writer.getHyperLink( + SectionName.METHODS_INHERITANCE, + configuration.getClassName(typeElement), writer.getResource("doclet.navMethod")); + } + } else { + return writer.getResource("doclet.navMethod"); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void addNavDetailLink(boolean link, Content liNav) { + if (link) { + liNav.addContent(writer.getHyperLink( + SectionName.METHOD_DETAIL, writer.getResource("doclet.navMethod"))); + } else { + liNav.addContent(writer.getResource("doclet.navMethod")); + } + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/NestedClassWriterImpl.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/NestedClassWriterImpl.java new file mode 100644 index 00000000000..c0828b8d5e2 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/NestedClassWriterImpl.java @@ -0,0 +1,241 @@ +/* + * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; + +import java.util.Arrays; +import java.util.List; + +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; + +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.MemberSummaryWriter; + +/** + * Writes nested class documentation in HTML format. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Robert Field + * @author Atul M Dambalkar + * @author Jamie Ho (rewrite) + * @author Bhavesh Patel (Modified) + */ +public class NestedClassWriterImpl extends AbstractMemberWriter + implements MemberSummaryWriter { + + public NestedClassWriterImpl(SubWriterHolderWriter writer, TypeElement typeElement) { + super(writer, typeElement); + } + + public NestedClassWriterImpl(SubWriterHolderWriter writer) { + super(writer); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getMemberSummaryHeader(TypeElement typeElement, + Content memberSummaryTree) { + memberSummaryTree.addContent(HtmlConstants.START_OF_NESTED_CLASS_SUMMARY); + Content memberTree = writer.getMemberTreeHeader(); + writer.addSummaryHeader(this, typeElement, memberTree); + return memberTree; + } + + /** + * {@inheritDoc} + */ + public void addMemberTree(Content memberSummaryTree, Content memberTree) { + writer.addMemberTree(memberSummaryTree, memberTree); + } + + /** + * Close the writer. + */ + @Override + public void close() throws IOException { + writer.close(); + } + + /** + * {@inheritDoc} + */ + @Override + public void addSummaryLabel(Content memberTree) { + Content label = HtmlTree.HEADING(HtmlConstants.SUMMARY_HEADING, + writer.getResource("doclet.Nested_Class_Summary")); + memberTree.addContent(label); + } + + /** + * {@inheritDoc} + */ + @Override + public String getTableSummary() { + return configuration.getText("doclet.Member_Table_Summary", + configuration.getText("doclet.Nested_Class_Summary"), + configuration.getText("doclet.nested_classes")); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getCaption() { + return configuration.getResource("doclet.Nested_Classes"); + } + + /** + * {@inheritDoc} + */ + @Override + public List getSummaryTableHeader(Element member) { + if (utils.isInterface(member)) { + return Arrays.asList(writer.getModifierTypeHeader(), + configuration.getText("doclet.0_and_1", + configuration.getText("doclet.Interface"), + configuration.getText("doclet.Description"))); + + } else { + return Arrays.asList(writer.getModifierTypeHeader(), + configuration.getText("doclet.0_and_1", + configuration.getText("doclet.Class"), + configuration.getText("doclet.Description"))); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void addSummaryAnchor(TypeElement typeElement, Content memberTree) { + memberTree.addContent(writer.getMarkerAnchor( + SectionName.NESTED_CLASS_SUMMARY)); + } + + /** + * {@inheritDoc} + */ + @Override + public void addInheritedSummaryAnchor(TypeElement typeElement, Content inheritedTree) { + inheritedTree.addContent(writer.getMarkerAnchor( + SectionName.NESTED_CLASSES_INHERITANCE, + utils.getFullyQualifiedName(typeElement))); + } + + /** + * {@inheritDoc} + */ + @Override + public void addInheritedSummaryLabel(TypeElement typeElement, Content inheritedTree) { + Content classLink = writer.getPreQualifiedClassLink( + LinkInfoImpl.Kind.MEMBER, typeElement, false); + Content label = new StringContent(utils.isInterface(typeElement) + ? configuration.getText("doclet.Nested_Classes_Interface_Inherited_From_Interface") + : configuration.getText("doclet.Nested_Classes_Interfaces_Inherited_From_Class")); + Content labelHeading = HtmlTree.HEADING(HtmlConstants.INHERITED_SUMMARY_HEADING, + label); + labelHeading.addContent(writer.getSpace()); + labelHeading.addContent(classLink); + inheritedTree.addContent(labelHeading); + } + + /** + * {@inheritDoc} + */ + @Override + protected void addSummaryLink(LinkInfoImpl.Kind context, TypeElement typeElement, Element member, + Content tdSummary) { + Content memberLink = HtmlTree.SPAN(HtmlStyle.memberNameLink, + writer.getLink(new LinkInfoImpl(configuration, context, (TypeElement)member))); + Content code = HtmlTree.CODE(memberLink); + tdSummary.addContent(code); + } + + /** + * {@inheritDoc} + */ + @Override + protected void addInheritedSummaryLink(TypeElement typeElement, Element member, Content linksTree) { + linksTree.addContent( + writer.getLink(new LinkInfoImpl(configuration, LinkInfoImpl.Kind.MEMBER, + (TypeElement)member))); + } + + /** + * {@inheritDoc} + */ + @Override + protected void addSummaryType(Element member, Content tdSummaryType) { + addModifierAndType(member, null, tdSummaryType); + } + + /** + * {@inheritDoc} + */ + @Override + protected Content getDeprecatedLink(Element member) { + return writer.getQualifiedClassLink(LinkInfoImpl.Kind.MEMBER, member); + } + + /** + * {@inheritDoc} + */ + @Override + protected Content getNavSummaryLink(TypeElement typeElement, boolean link) { + if (link) { + if (typeElement == null) { + return writer.getHyperLink( + SectionName.NESTED_CLASS_SUMMARY, + writer.getResource("doclet.navNested")); + } else { + return writer.getHyperLink( + SectionName.NESTED_CLASSES_INHERITANCE, + utils.getFullyQualifiedName(typeElement), writer.getResource("doclet.navNested")); + } + } else { + return writer.getResource("doclet.navNested"); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void addNavDetailLink(boolean link, Content liNav) { + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PackageFrameWriter.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PackageFrameWriter.java new file mode 100644 index 00000000000..4925b55d00b --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PackageFrameWriter.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; +import java.util.*; + +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; + +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.Configuration; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; +import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; + + +/** + * Class to generate file for each package contents in the left-hand bottom + * frame. This will list all the Class Kinds in the package. A click on any + * class-kind will update the right-hand frame with the clicked class-kind page. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Atul M Dambalkar + * @author Bhavesh Patel (Modified) + */ +public class PackageFrameWriter extends HtmlDocletWriter { + + /** + * The package being documented. + */ + private PackageElement packageElement; + + /** + * The classes to be documented. Use this to filter out classes + * that will not be documented. + */ + private SortedSet documentedClasses; + + /** + * Constructor to construct PackageFrameWriter object and to generate + * "package-frame.html" file in the respective package directory. + * For example for package "java.lang" this will generate file + * "package-frame.html" file in the "java/lang" directory. It will also + * create "java/lang" directory in the current or the destination directory + * if it doesn't exist. + * + * @param configuration the configuration of the doclet. + * @param packageElement PackageElement under consideration. + */ + public PackageFrameWriter(ConfigurationImpl configuration, PackageElement packageElement) + throws IOException { + super(configuration, DocPath.forPackage(packageElement).resolve(DocPaths.PACKAGE_FRAME)); + this.packageElement = packageElement; + if (utils.getSpecifiedPackages().isEmpty()) { + documentedClasses = new TreeSet<>(utils.makeGeneralPurposeComparator()); + documentedClasses.addAll(configuration.root.getIncludedClasses()); + } + } + + /** + * Generate a package summary page for the left-hand bottom frame. Construct + * the PackageFrameWriter object and then uses it generate the file. + * + * @param configuration the current configuration of the doclet. + * @param packageElement The package for which "pacakge-frame.html" is to be generated. + */ + public static void generate(ConfigurationImpl configuration, PackageElement packageElement) { + PackageFrameWriter packgen; + try { + packgen = new PackageFrameWriter(configuration, packageElement); + String pkgName = configuration.utils.getPackageName(packageElement); + HtmlTree body = packgen.getBody(false, packgen.getWindowTitle(pkgName)); + Content pkgNameContent = new StringContent(pkgName); + HtmlTree htmlTree = (configuration.allowTag(HtmlTag.MAIN)) + ? HtmlTree.MAIN() + : body; + Content heading = HtmlTree.HEADING(HtmlConstants.TITLE_HEADING, HtmlStyle.bar, + packgen.getTargetPackageLink(packageElement, "classFrame", pkgNameContent)); + htmlTree.addContent(heading); + HtmlTree div = new HtmlTree(HtmlTag.DIV); + div.addStyle(HtmlStyle.indexContainer); + packgen.addClassListing(div); + htmlTree.addContent(div); + if (configuration.allowTag(HtmlTag.MAIN)) { + body.addContent(htmlTree); + } + packgen.printHtmlDocument( + configuration.metakeywords.getMetaKeywords(packageElement), false, body); + packgen.close(); + } catch (IOException exc) { + configuration.standardmessage.error( + "doclet.exception_encountered", + exc.toString(), DocPaths.PACKAGE_FRAME.getPath()); + throw new DocletAbortException(exc); + } + } + + /** + * Add class listing for all the classes in this package. Divide class + * listing as per the class kind and generate separate listing for + * Classes, Interfaces, Exceptions and Errors. + * + * @param contentTree the content tree to which the listing will be added + */ + protected void addClassListing(HtmlTree contentTree) { + Configuration config = configuration; + if (utils.isIncluded(packageElement)) { + addClassKindListing(utils.getInterfaces(packageElement), + getResource("doclet.Interfaces"), contentTree); + addClassKindListing(utils.getOrdinaryClasses(packageElement), + getResource("doclet.Classes"), contentTree); + addClassKindListing(utils.getEnums(packageElement), + getResource("doclet.Enums"), contentTree); + addClassKindListing(utils.getExceptions(packageElement), + getResource("doclet.Exceptions"), contentTree); + addClassKindListing(utils.getErrors(packageElement), + getResource("doclet.Errors"), contentTree); + addClassKindListing(utils.getAnnotationTypes(packageElement), + getResource("doclet.AnnotationTypes"), contentTree); + } else { + addClassKindListing(config.typeElementCatalog.interfaces(packageElement), + getResource("doclet.Interfaces"), contentTree); + addClassKindListing(config.typeElementCatalog.ordinaryClasses(packageElement), + getResource("doclet.Classes"), contentTree); + addClassKindListing(config.typeElementCatalog.enums(packageElement), + getResource("doclet.Enums"), contentTree); + addClassKindListing(config.typeElementCatalog.exceptions(packageElement), + getResource("doclet.Exceptions"), contentTree); + addClassKindListing(config.typeElementCatalog.errors(packageElement), + getResource("doclet.Errors"), contentTree); + addClassKindListing(config.typeElementCatalog.annotationTypes(packageElement), + getResource("doclet.AnnotationTypes"), contentTree); + } + } + + /** + * Add specific class kind listing. Also add label to the listing. + * + * @param arr Array of specific class kinds, namely Class or Interface or Exception or Error + * @param labelContent content tree of the label to be added + * @param contentTree the content tree to which the class kind listing will be added + */ + protected void addClassKindListing(Iterable list, Content labelContent, + HtmlTree contentTree) { + SortedSet tset = utils.filterOutPrivateClasses(list, configuration.javafx); + if(!tset.isEmpty()) { + boolean printedHeader = false; + HtmlTree htmlTree = (configuration.allowTag(HtmlTag.SECTION)) + ? HtmlTree.SECTION() + : contentTree; + HtmlTree ul = new HtmlTree(HtmlTag.UL); + ul.setTitle(labelContent); + for (TypeElement typeElement : tset) { + if (documentedClasses != null && !documentedClasses.contains(typeElement)) { + continue; + } + if (!utils.isCoreClass(typeElement) || !configuration.isGeneratedDoc(typeElement)) { + continue; + } + if (!printedHeader) { + Content heading = HtmlTree.HEADING(HtmlConstants.CONTENT_HEADING, + true, labelContent); + htmlTree.addContent(heading); + printedHeader = true; + } + Content arr_i_name = new StringContent(utils.getSimpleName(typeElement)); + if (utils.isInterface(typeElement)) + arr_i_name = HtmlTree.SPAN(HtmlStyle.interfaceName, arr_i_name); + Content link = getLink(new LinkInfoImpl(configuration, + LinkInfoImpl.Kind.PACKAGE_FRAME, typeElement).label(arr_i_name).target("classFrame")); + Content li = HtmlTree.LI(link); + ul.addContent(li); + } + htmlTree.addContent(ul); + if (configuration.allowTag(HtmlTag.SECTION)) { + contentTree.addContent(htmlTree); + } + } + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PackageIndexFrameWriter.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PackageIndexFrameWriter.java new file mode 100644 index 00000000000..064a93ce9d2 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PackageIndexFrameWriter.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.IOException; +import java.util.Collection; + +import javax.lang.model.element.PackageElement; + +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.RawHtml; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; +import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; + + +/** + * Generate the package index for the left-hand frame in the generated output. + * A click on the package name in this frame will update the page in the bottom + * left hand frame with the listing of contents of the clicked package. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Atul M Dambalkar + */ +public class PackageIndexFrameWriter extends AbstractPackageIndexWriter { + + /** + * Construct the PackageIndexFrameWriter object. + * + * @param filename Name of the package index file to be generated. + */ + public PackageIndexFrameWriter(ConfigurationImpl configuration, + DocPath filename) throws IOException { + super(configuration, filename); + } + + /** + * Generate the package index file named "overview-frame.html". + * @throws DocletAbortException + */ + public static void generate(ConfigurationImpl configuration) { + PackageIndexFrameWriter packgen; + DocPath filename = DocPaths.OVERVIEW_FRAME; + try { + packgen = new PackageIndexFrameWriter(configuration, filename); + packgen.buildPackageIndexFile("doclet.Window_Overview", false); + packgen.close(); + } catch (IOException exc) { + configuration.standardmessage.error( + "doclet.exception_encountered", + exc.toString(), filename); + throw new DocletAbortException(exc); + } + } + + /** + * {@inheritDoc} + */ + protected void addPackagesList(Collection packages, String text, + String tableSummary, Content body) { + Content heading = HtmlTree.HEADING(HtmlConstants.PACKAGE_HEADING, true, + packagesLabel); + HtmlTree htmlTree = (configuration.allowTag(HtmlTag.MAIN)) + ? HtmlTree.MAIN(HtmlStyle.indexContainer, heading) + : HtmlTree.DIV(HtmlStyle.indexContainer, heading); + HtmlTree ul = new HtmlTree(HtmlTag.UL); + ul.setTitle(packagesLabel); + for (PackageElement aPackage : packages) { + // Do not list the package if -nodeprecated option is set and the + // package is marked as deprecated. + if (aPackage != null && + (!(configuration.nodeprecated && utils.isDeprecated(aPackage)))) { + ul.addContent(getPackage(aPackage)); + } + } + htmlTree.addContent(ul); + body.addContent(htmlTree); + } + + /** + * Returns each package name as a separate link. + * + * @param pe PackageElement + * @return content for the package link + */ + protected Content getPackage(PackageElement pe) { + Content packageLinkContent; + Content packageLabel; + if (pe.isUnnamed()) { + packageLabel = new StringContent(""); + packageLinkContent = getHyperLink(DocPaths.PACKAGE_FRAME, + packageLabel, "", "packageFrame"); + } else { + packageLabel = getPackageLabel(pe.getQualifiedName().toString()); + packageLinkContent = getHyperLink(pathString(pe, + DocPaths.PACKAGE_FRAME), packageLabel, "", + "packageFrame"); + } + Content li = HtmlTree.LI(packageLinkContent); + return li; + } + + /** + * {@inheritDoc} + */ + protected void addNavigationBarHeader(Content body) { + Content headerContent; + if (configuration.packagesheader.length() > 0) { + headerContent = new RawHtml(replaceDocRootDir(configuration.packagesheader)); + } else { + headerContent = new RawHtml(replaceDocRootDir(configuration.header)); + } + Content heading = HtmlTree.HEADING(HtmlConstants.TITLE_HEADING, true, + HtmlStyle.bar, headerContent); + body.addContent(heading); + } + + /** + * Do nothing as there is no overview information in this page. + */ + protected void addOverviewHeader(Content body) { + } + + /** + * Adds "All Classes" link for the top of the left-hand frame page to the + * documentation tree. + * + * @param ul the Content object to which the "All Classes" link should be added + */ + protected void addAllClassesLink(Content ul) { + Content linkContent = getHyperLink(DocPaths.ALLCLASSES_FRAME, + allclassesLabel, "", "packageFrame"); + Content li = HtmlTree.LI(linkContent); + ul.addContent(li); + } + + /** + * {@inheritDoc} + */ + protected void addNavigationBarFooter(Content body) { + Content p = HtmlTree.P(getSpace()); + body.addContent(p); + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PackageIndexWriter.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PackageIndexWriter.java new file mode 100644 index 00000000000..c9a3a987038 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PackageIndexWriter.java @@ -0,0 +1,274 @@ +/* + * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; +import java.util.*; + +import javax.lang.model.element.PackageElement; + +import jdk.javadoc.doclet.DocletEnvironment; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.RawHtml; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; +import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; +import jdk.javadoc.internal.doclets.toolkit.util.Group; + +/** + * Generate the package index page "overview-summary.html" for the right-hand + * frame. A click on the package name on this page will update the same frame + * with the "package-summary.html" file for the clicked package. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Atul M Dambalkar + * @author Bhavesh Patel (Modified) + */ +public class PackageIndexWriter extends AbstractPackageIndexWriter { + + /** + * Root of the program structure. Used for "overview" documentation. + */ + private DocletEnvironment root; + + /** + * Map representing the group of packages as specified on the command line. + * + * @see Group + */ + private Map> groupPackageMap; + + /** + * List to store the order groups as specified on the command line. + */ + private List groupList; + + /** + * HTML tree for main tag. + */ + private HtmlTree htmlTree = HtmlTree.MAIN(); + + /** + * Construct the PackageIndexWriter. Also constructs the grouping + * information as provided on the command line by "-group" option. Stores + * the order of groups specified by the user. + * + * @see Group + */ + public PackageIndexWriter(ConfigurationImpl configuration, DocPath filename) throws IOException { + super(configuration, filename); + this.root = configuration.root; + groupPackageMap = configuration.group.groupPackages(packages); + groupList = configuration.group.getGroupList(); + } + + /** + * Generate the package index page for the right-hand frame. + * + * @param configuration the current configuration of the doclet. + */ + public static void generate(ConfigurationImpl configuration) { + PackageIndexWriter packgen; + DocPath filename = DocPaths.OVERVIEW_SUMMARY; + try { + packgen = new PackageIndexWriter(configuration, filename); + packgen.buildPackageIndexFile("doclet.Window_Overview_Summary", true); + packgen.close(); + } catch (IOException exc) { + configuration.standardmessage.error( + "doclet.exception_encountered", + exc.toString(), filename); + throw new DocletAbortException(exc); + } + } + + /** + * Depending upon the grouping information and their titles, add + * separate table indices for each package group. + * + * @param body the documentation tree to which the index will be added + */ + protected void addIndex(Content body) { + for (String groupname : groupList) { + SortedSet list = groupPackageMap.get(groupname); + if (list != null && !list.isEmpty()) { + addIndexContents(list, + groupname, configuration.getText("doclet.Member_Table_Summary", + groupname, configuration.getText("doclet.packages")), body); + } + } + } + + /** + * {@inheritDoc} + */ + protected void addPackagesList(Collection packages, String text, + String tableSummary, Content body) { + Content table = (configuration.isOutputHtml5()) + ? HtmlTree.TABLE(HtmlStyle.overviewSummary, getTableCaption(new RawHtml(text))) + : HtmlTree.TABLE(HtmlStyle.overviewSummary, tableSummary, getTableCaption(new RawHtml(text))); + table.addContent(getSummaryTableHeader(packageTableHeader, "col")); + Content tbody = new HtmlTree(HtmlTag.TBODY); + addPackagesList(packages, tbody); + table.addContent(tbody); + Content div = HtmlTree.DIV(HtmlStyle.contentContainer, table); + if (configuration.allowTag(HtmlTag.MAIN)) { + htmlTree.addContent(div); + } else { + body.addContent(div); + } + } + + /** + * Adds list of packages in the index table. Generate link to each package. + * + * @param packages Packages to which link is to be generated + * @param tbody the documentation tree to which the list will be added + */ + protected void addPackagesList(Collection packages, Content tbody) { + boolean altColor = true; + for (PackageElement pkg : packages) { + if (!pkg.isUnnamed()) { + if (!(configuration.nodeprecated && utils.isDeprecated(pkg))) { + Content packageLinkContent = getPackageLink(pkg, getPackageName(pkg)); + Content tdPackage = HtmlTree.TD(HtmlStyle.colFirst, packageLinkContent); + HtmlTree tdSummary = new HtmlTree(HtmlTag.TD); + tdSummary.addStyle(HtmlStyle.colLast); + addSummaryComment(pkg, tdSummary); + HtmlTree tr = HtmlTree.TR(tdPackage); + tr.addContent(tdSummary); + tr.addStyle(altColor ? HtmlStyle.altColor : HtmlStyle.rowColor); + tbody.addContent(tr); + } + } + altColor = !altColor; + } + } + + /** + * Adds the overview summary comment for this documentation. Add one line + * summary at the top of the page and generate a link to the description, + * which is added at the end of this page. + * + * @param body the documentation tree to which the overview header will be added + */ + protected void addOverviewHeader(Content body) { + addConfigurationTitle(body); + if (!utils.getBody(configuration.overviewElement).isEmpty()) { + HtmlTree subTitleDiv = new HtmlTree(HtmlTag.DIV); + subTitleDiv.addStyle(HtmlStyle.subTitle); + addSummaryComment(configuration.overviewElement, subTitleDiv); + Content div = HtmlTree.DIV(HtmlStyle.header, subTitleDiv); + Content see = seeLabel; + see.addContent(" "); + Content descPara = HtmlTree.P(see); + Content descLink = getHyperLink(getDocLink( + SectionName.OVERVIEW_DESCRIPTION), + descriptionLabel, "", ""); + descPara.addContent(descLink); + div.addContent(descPara); + if (configuration.allowTag(HtmlTag.MAIN)) { + htmlTree.addContent(div); + } else { + body.addContent(div); + } + } + } + + /** + * Adds the overview comment as provided in the file specified by the + * "-overview" option on the command line. + * + * @param htmltree the documentation tree to which the overview comment will + * be added + */ + protected void addOverviewComment(Content htmltree) { + if (!utils.getBody(configuration.overviewElement).isEmpty()) { + htmltree.addContent(getMarkerAnchor(SectionName.OVERVIEW_DESCRIPTION)); + addInlineComment(configuration.overviewElement, htmltree); + } + } + + /** + * Adds the tag information as provided in the file specified by the + * "-overview" option on the command line. + * + * @param body the documentation tree to which the overview will be added + */ + protected void addOverview(Content body) throws IOException { + HtmlTree div = new HtmlTree(HtmlTag.DIV); + div.addStyle(HtmlStyle.contentContainer); + addOverviewComment(div); + if (configuration.allowTag(HtmlTag.MAIN)) { + htmlTree.addContent(div); + body.addContent(htmlTree); + } else { + body.addContent(div); + } + } + + /** + * Adds the top text (from the -top option), the upper + * navigation bar, and then the title (from the"-title" + * option), at the top of page. + * + * @param body the documentation tree to which the navigation bar header will be added + */ + protected void addNavigationBarHeader(Content body) { + Content htmlTree = (configuration.allowTag(HtmlTag.HEADER)) + ? HtmlTree.HEADER() + : body; + addTop(htmlTree); + addNavLinks(true, htmlTree); + if (configuration.allowTag(HtmlTag.HEADER)) { + body.addContent(htmlTree); + } + } + + /** + * Adds the lower navigation bar and the bottom text + * (from the -bottom option) at the bottom of page. + * + * @param body the documentation tree to which the navigation bar footer will be added + */ + protected void addNavigationBarFooter(Content body) { + Content htmlTree = (configuration.allowTag(HtmlTag.FOOTER)) + ? HtmlTree.FOOTER() + : body; + addNavLinks(false, htmlTree); + addBottom(htmlTree); + if (configuration.allowTag(HtmlTag.FOOTER)) { + body.addContent(htmlTree); + } + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PackageTreeWriter.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PackageTreeWriter.java new file mode 100644 index 00000000000..9dac9a3bf18 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PackageTreeWriter.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; + +import javax.lang.model.element.PackageElement; + +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.ClassTree; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; +import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; + + +/** + * Class to generate Tree page for a package. The name of the file generated is + * "package-tree.html" and it is generated in the respective package directory. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Atul M Dambalkar + * @author Bhavesh Patel (Modified) + */ +public class PackageTreeWriter extends AbstractTreeWriter { + + /** + * Package for which tree is to be generated. + */ + protected PackageElement packageElement; + + /** + * The previous package name in the alpha-order list. + */ + protected PackageElement prev; + + /** + * The next package name in the alpha-order list. + */ + protected PackageElement next; + + /** + * Constructor. + * @param configuration the configuration + * @param path the docpath to generate files into + * @param packageElement the current package + * @param prev the previous package + * @param next the next package + * @throws IOException + * @throws DocletAbortException + */ + public PackageTreeWriter(ConfigurationImpl configuration, + DocPath path, + PackageElement packageElement, + PackageElement prev, PackageElement next) + throws IOException { + super(configuration, path, + new ClassTree(configuration.typeElementCatalog.allClasses(packageElement), configuration)); + this.packageElement = packageElement; + this.prev = prev; + this.next = next; + } + + /** + * Construct a PackageTreeWriter object and then use it to generate the + * package tree page. + * + * @param configuration the configuration for this run. + * @param pkg Package for which tree file is to be generated. + * @param prev Previous package in the alpha-ordered list. + * @param next Next package in the alpha-ordered list. + * @param noDeprecated If true, do not generate any information for + * deprecated classe or interfaces. + * @throws DocletAbortException + */ + public static void generate(ConfigurationImpl configuration, + PackageElement pkg, PackageElement prev, + PackageElement next, boolean noDeprecated) { + PackageTreeWriter packgen; + DocPath path = DocPath.forPackage(pkg).resolve(DocPaths.PACKAGE_TREE); + try { + packgen = new PackageTreeWriter(configuration, path, pkg, + prev, next); + packgen.generatePackageTreeFile(); + packgen.close(); + } catch (IOException exc) { + configuration.standardmessage.error( + "doclet.exception_encountered", + exc.toString(), path.getPath()); + throw new DocletAbortException(exc); + } + } + + /** + * Generate a separate tree file for each package. + * @throws java.io.IOException + */ + protected void generatePackageTreeFile() throws IOException { + HtmlTree body = getPackageTreeHeader(); + HtmlTree htmlTree = (configuration.allowTag(HtmlTag.MAIN)) + ? HtmlTree.MAIN() + : body; + Content headContent = getResource("doclet.Hierarchy_For_Package", + utils.getPackageName(packageElement)); + Content heading = HtmlTree.HEADING(HtmlConstants.TITLE_HEADING, false, + HtmlStyle.title, headContent); + Content div = HtmlTree.DIV(HtmlStyle.header, heading); + if (configuration.packages.size() > 1) { + addLinkToMainTree(div); + } + htmlTree.addContent(div); + HtmlTree divTree = new HtmlTree(HtmlTag.DIV); + divTree.addStyle(HtmlStyle.contentContainer); + addTree(classtree.baseClasses(), "doclet.Class_Hierarchy", divTree); + addTree(classtree.baseInterfaces(), "doclet.Interface_Hierarchy", divTree); + addTree(classtree.baseAnnotationTypes(), "doclet.Annotation_Type_Hierarchy", divTree); + addTree(classtree.baseEnums(), "doclet.Enum_Hierarchy", divTree, true); + htmlTree.addContent(divTree); + if (configuration.allowTag(HtmlTag.MAIN)) { + body.addContent(htmlTree); + } + HtmlTree tree = (configuration.allowTag(HtmlTag.FOOTER)) + ? HtmlTree.FOOTER() + : body; + addNavLinks(false, tree); + addBottom(tree); + if (configuration.allowTag(HtmlTag.FOOTER)) { + body.addContent(tree); + } + printHtmlDocument(null, true, body); + } + + /** + * Get the package tree header. + * + * @return a content tree for the header + */ + protected HtmlTree getPackageTreeHeader() { + String packageName = packageElement.isUnnamed() ? "" : utils.getPackageName(packageElement); + String title = packageName + " " + configuration.getText("doclet.Window_Class_Hierarchy"); + HtmlTree bodyTree = getBody(true, getWindowTitle(title)); + HtmlTree htmlTree = (configuration.allowTag(HtmlTag.HEADER)) + ? HtmlTree.HEADER() + : bodyTree; + addTop(htmlTree); + addNavLinks(true, htmlTree); + if (configuration.allowTag(HtmlTag.HEADER)) { + bodyTree.addContent(htmlTree); + } + return bodyTree; + } + + /** + * Add a link to the tree for all the packages. + * + * @param div the content tree to which the link will be added + */ + protected void addLinkToMainTree(Content div) { + Content span = HtmlTree.SPAN(HtmlStyle.packageHierarchyLabel, + getResource("doclet.Package_Hierarchies")); + div.addContent(span); + HtmlTree ul = new HtmlTree (HtmlTag.UL); + ul.addStyle(HtmlStyle.horizontal); + ul.addContent(getNavLinkMainTree(configuration.getText("doclet.All_Packages"))); + div.addContent(ul); + } + + /** + * Get link for the previous package tree file. + * + * @return a content tree for the link + */ + protected Content getNavLinkPrevious() { + if (prev == null) { + return getNavLinkPrevious(null); + } else { + DocPath path = DocPath.relativePath(packageElement, prev); + return getNavLinkPrevious(path.resolve(DocPaths.PACKAGE_TREE)); + } + } + + /** + * Get link for the next package tree file. + * + * @return a content tree for the link + */ + protected Content getNavLinkNext() { + if (next == null) { + return getNavLinkNext(null); + } else { + DocPath path = DocPath.relativePath(packageElement, next); + return getNavLinkNext(path.resolve(DocPaths.PACKAGE_TREE)); + } + } + + /** + * Get link to the package summary page for the package of this tree. + * + * @return a content tree for the package link + */ + protected Content getNavLinkPackage() { + Content linkContent = getHyperLink(DocPaths.PACKAGE_SUMMARY, + packageLabel); + Content li = HtmlTree.LI(linkContent); + return li; + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PackageUseWriter.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PackageUseWriter.java new file mode 100644 index 00000000000..3763e725892 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PackageUseWriter.java @@ -0,0 +1,339 @@ +/* + * Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; +import java.util.*; + +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; + +import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.ClassUseMapper; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; +import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; + +/** + * Generate package usage information. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Robert G. Field + * @author Bhavesh Patel (Modified) + */ +public class PackageUseWriter extends SubWriterHolderWriter { + + final PackageElement packageElement; + final SortedMap> usingPackageToUsedClasses = new TreeMap<>(); + protected HtmlTree mainTree = HtmlTree.MAIN(); + + /** + * Constructor. + * + * @param filename the file to be generated. + * @throws IOException + * @throws DocletAbortException + */ + public PackageUseWriter(ConfigurationImpl configuration, + ClassUseMapper mapper, DocPath filename, + PackageElement pkgElement) throws IOException { + super(configuration, DocPath.forPackage(pkgElement).resolve(filename)); + this.packageElement = pkgElement; + + // by examining all classes in this package, find what packages + // use these classes - produce a map between using package and + // used classes. + for (TypeElement usedClass : utils.getEnclosedTypeElements(pkgElement)) { + Set usingClasses = mapper.classToClass.get(usedClass); + if (usingClasses != null) { + for (TypeElement usingClass : usingClasses) { + PackageElement usingPackage = utils.containingPackage(usingClass); + Set usedClasses = usingPackageToUsedClasses + .get(utils.getPackageName(usingPackage)); + if (usedClasses == null) { + usedClasses = new TreeSet<>(utils.makeGeneralPurposeComparator()); + usingPackageToUsedClasses.put(utils.getPackageName(usingPackage), + usedClasses); + } + usedClasses.add(usedClass); + } + } + } + } + + /** + * Generate a class page. + * + * @param configuration the current configuration of the doclet. + * @param mapper the mapping of the class usage. + * @param pkgElement the package being documented. + */ + public static void generate(ConfigurationImpl configuration, + ClassUseMapper mapper, PackageElement pkgElement) { + PackageUseWriter pkgusegen; + DocPath filename = DocPaths.PACKAGE_USE; + try { + pkgusegen = new PackageUseWriter(configuration, mapper, filename, pkgElement); + pkgusegen.generatePackageUseFile(); + pkgusegen.close(); + } catch (IOException exc) { + configuration.standardmessage.error( + "doclet.exception_encountered", + exc.toString(), filename); + throw new DocletAbortException(exc); + } + } + + /** + * Generate the package use list. + */ + protected void generatePackageUseFile() throws IOException { + HtmlTree body = getPackageUseHeader(); + HtmlTree div = new HtmlTree(HtmlTag.DIV); + div.addStyle(HtmlStyle.contentContainer); + if (usingPackageToUsedClasses.isEmpty()) { + div.addContent(getResource("doclet.ClassUse_No.usage.of.0", utils.getPackageName(packageElement))); + } else { + addPackageUse(div); + } + if (configuration.allowTag(HtmlTag.MAIN)) { + mainTree.addContent(div); + body.addContent(mainTree); + } else { + body.addContent(div); + } + HtmlTree tree = (configuration.allowTag(HtmlTag.FOOTER)) + ? HtmlTree.FOOTER() + : body; + addNavLinks(false, tree); + addBottom(tree); + if (configuration.allowTag(HtmlTag.FOOTER)) { + body.addContent(tree); + } + printHtmlDocument(null, true, body); + } + + /** + * Add the package use information. + * + * @param contentTree the content tree to which the package use information will be added + */ + protected void addPackageUse(Content contentTree) throws IOException { + HtmlTree ul = new HtmlTree(HtmlTag.UL); + ul.addStyle(HtmlStyle.blockList); + if (configuration.packages.size() > 1) { + addPackageList(ul); + } + addClassList(ul); + contentTree.addContent(ul); + } + + /** + * Add the list of packages that use the given package. + * + * @param contentTree the content tree to which the package list will be added + */ + protected void addPackageList(Content contentTree) throws IOException { + Content caption = getTableCaption(configuration.getResource( + "doclet.ClassUse_Packages.that.use.0", + getPackageLink(packageElement, utils.getPackageName(packageElement)))); + Content table = (configuration.isOutputHtml5()) + ? HtmlTree.TABLE(HtmlStyle.useSummary, caption) + : HtmlTree.TABLE(HtmlStyle.useSummary, useTableSummary, caption); + table.addContent(getSummaryTableHeader(packageTableHeader, "col")); + Content tbody = new HtmlTree(HtmlTag.TBODY); + boolean altColor = true; + for (String pkgname: usingPackageToUsedClasses.keySet()) { + PackageElement pkg = utils.elementUtils.getPackageElement(pkgname); + HtmlTree tr = new HtmlTree(HtmlTag.TR); + tr.addStyle(altColor ? HtmlStyle.altColor : HtmlStyle.rowColor); + altColor = !altColor; + addPackageUse(pkg, tr); + tbody.addContent(tr); + } + table.addContent(tbody); + Content li = HtmlTree.LI(HtmlStyle.blockList, table); + contentTree.addContent(li); + } + + /** + * Add the list of classes that use the given package. + * + * @param contentTree the content tree to which the class list will be added + */ + protected void addClassList(Content contentTree) throws IOException { + List classTableHeader = Arrays.asList( + configuration.getText("doclet.0_and_1", + configuration.getText("doclet.Class"), + configuration.getText("doclet.Description"))); + for (String packageName : usingPackageToUsedClasses.keySet()) { + PackageElement usingPackage = utils.elementUtils.getPackageElement(packageName); + HtmlTree li = new HtmlTree(HtmlTag.LI); + li.addStyle(HtmlStyle.blockList); + if (usingPackage != null) { + li.addContent(getMarkerAnchor(utils.getPackageName(usingPackage))); + } + String tableSummary = configuration.getText("doclet.Use_Table_Summary", + configuration.getText("doclet.classes")); + Content caption = getTableCaption(configuration.getResource( + "doclet.ClassUse_Classes.in.0.used.by.1", + getPackageLink(packageElement, utils.getPackageName(packageElement)), + getPackageLink(usingPackage, utils.getPackageName(usingPackage)))); + Content table = (configuration.isOutputHtml5()) + ? HtmlTree.TABLE(HtmlStyle.useSummary, caption) + : HtmlTree.TABLE(HtmlStyle.useSummary, tableSummary, caption); + table.addContent(getSummaryTableHeader(classTableHeader, "col")); + Content tbody = new HtmlTree(HtmlTag.TBODY); + boolean altColor = true; + for (TypeElement te : usingPackageToUsedClasses.get(packageName)) { + HtmlTree tr = new HtmlTree(HtmlTag.TR); + tr.addStyle(altColor ? HtmlStyle.altColor : HtmlStyle.rowColor); + altColor = !altColor; + addClassRow(te, usingPackage, tr); + tbody.addContent(tr); + } + table.addContent(tbody); + li.addContent(table); + contentTree.addContent(li); + } + } + + /** + * Add a row for the class that uses the given package. + * + * @param usedClass the class that uses the given package + * @param pkg the package to which the class belongs + * @param contentTree the content tree to which the row will be added + */ + protected void addClassRow(TypeElement usedClass, PackageElement pkg, + Content contentTree) { + DocPath dp = pathString(usedClass, + DocPaths.CLASS_USE.resolve(DocPath.forName(utils, usedClass))); + StringContent stringContent = new StringContent(utils.getSimpleName(usedClass)); + Content td = HtmlTree.TD(HtmlStyle.colOne, + getHyperLink(dp.fragment(getPackageAnchorName(pkg)), stringContent)); + addIndexComment(usedClass, td); + contentTree.addContent(td); + } + + /** + * Add the package use information. + * + * @param pkg the package that used the given package + * @param contentTree the content tree to which the information will be added + */ + protected void addPackageUse(PackageElement pkg, Content contentTree) throws IOException { + Content tdFirst = HtmlTree.TD(HtmlStyle.colFirst, + getHyperLink(utils.getPackageName(pkg), + new StringContent(utils.getPackageName(pkg)))); + contentTree.addContent(tdFirst); + HtmlTree tdLast = new HtmlTree(HtmlTag.TD); + tdLast.addStyle(HtmlStyle.colLast); + if (pkg != null && !pkg.isUnnamed()) { + addSummaryComment(pkg, tdLast); + } else { + tdLast.addContent(getSpace()); + } + contentTree.addContent(tdLast); + } + + /** + * Get the header for the package use listing. + * + * @return a content tree representing the package use header + */ + protected HtmlTree getPackageUseHeader() { + String packageText = configuration.getText("doclet.Package"); + String name = packageElement.isUnnamed() ? "" : utils.getPackageName(packageElement); + String title = configuration.getText("doclet.Window_ClassUse_Header", packageText, name); + HtmlTree bodyTree = getBody(true, getWindowTitle(title)); + HtmlTree htmlTree = (configuration.allowTag(HtmlTag.HEADER)) + ? HtmlTree.HEADER() + : bodyTree; + addTop(htmlTree); + addNavLinks(true, htmlTree); + if (configuration.allowTag(HtmlTag.HEADER)) { + bodyTree.addContent(htmlTree); + } + ContentBuilder headContent = new ContentBuilder(); + headContent.addContent(getResource("doclet.ClassUse_Title", packageText)); + headContent.addContent(new HtmlTree(HtmlTag.BR)); + headContent.addContent(name); + Content heading = HtmlTree.HEADING(HtmlConstants.TITLE_HEADING, true, + HtmlStyle.title, headContent); + Content div = HtmlTree.DIV(HtmlStyle.header, heading); + if (configuration.allowTag(HtmlTag.MAIN)) { + mainTree.addContent(div); + } else { + bodyTree.addContent(div); + } + return bodyTree; + } + + /** + * Get this package link. + * + * @return a content tree for the package link + */ + protected Content getNavLinkPackage() { + Content linkContent = getHyperLink(DocPaths.PACKAGE_SUMMARY, + packageLabel); + Content li = HtmlTree.LI(linkContent); + return li; + } + + /** + * Get the use link. + * + * @return a content tree for the use link + */ + protected Content getNavLinkClassUse() { + Content li = HtmlTree.LI(HtmlStyle.navBarCell1Rev, useLabel); + return li; + } + + /** + * Get the tree link. + * + * @return a content tree for the tree link + */ + protected Content getNavLinkTree() { + Content linkContent = getHyperLink(DocPaths.PACKAGE_TREE, + treeLabel); + Content li = HtmlTree.LI(linkContent); + return li; + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PackageWriterImpl.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PackageWriterImpl.java new file mode 100644 index 00000000000..8689b0f76e8 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PackageWriterImpl.java @@ -0,0 +1,377 @@ +/* + * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; +import java.util.*; + +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; + +import com.sun.source.doctree.DocTree; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.RawHtml; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.PackageSummaryWriter; +import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; + +/** + * Class to generate file for each package contents in the right-hand + * frame. This will list all the Class Kinds in the package. A click on any + * class-kind will update the frame with the clicked class-kind page. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Atul M Dambalkar + * @author Bhavesh Patel (Modified) + */ +public class PackageWriterImpl extends HtmlDocletWriter + implements PackageSummaryWriter { + + /** + * The prev package name in the alpha-order list. + */ + protected PackageElement prev; + + /** + * The next package name in the alpha-order list. + */ + protected PackageElement next; + + /** + * The package being documented. + */ + protected PackageElement packageElement; + + /** + * The HTML tree for main tag. + */ + protected HtmlTree mainTree = HtmlTree.MAIN(); + + /** + * The HTML tree for section tag. + */ + protected HtmlTree sectionTree = HtmlTree.SECTION(); + + /** + * Constructor to construct PackageWriter object and to generate + * "package-summary.html" file in the respective package directory. + * For example for package "java.lang" this will generate file + * "package-summary.html" file in the "java/lang" directory. It will also + * create "java/lang" directory in the current or the destination directory + * if it doesn't exist. + * + * @param configuration the configuration of the doclet. + * @param packageElement PackageElement under consideration. + * @param prev Previous package in the sorted array. + * @param next Next package in the sorted array. + */ + public PackageWriterImpl(ConfigurationImpl configuration, + PackageElement packageElement, PackageElement prev, PackageElement next) + throws IOException { + super(configuration, DocPath + .forPackage(packageElement) + .resolve(DocPaths.PACKAGE_SUMMARY)); + this.prev = prev; + this.next = next; + this.packageElement = packageElement; + } + + /** + * {@inheritDoc} + */ + public Content getPackageHeader(String heading) { + HtmlTree bodyTree = getBody(true, getWindowTitle(utils.getPackageName(packageElement))); + HtmlTree htmlTree = (configuration.allowTag(HtmlTag.HEADER)) + ? HtmlTree.HEADER() + : bodyTree; + addTop(htmlTree); + addNavLinks(true, htmlTree); + if (configuration.allowTag(HtmlTag.HEADER)) { + bodyTree.addContent(htmlTree); + } + HtmlTree div = new HtmlTree(HtmlTag.DIV); + div.addStyle(HtmlStyle.header); + Content annotationContent = new HtmlTree(HtmlTag.P); + addAnnotationInfo(packageElement, annotationContent); + div.addContent(annotationContent); + Content tHeading = HtmlTree.HEADING(HtmlConstants.TITLE_HEADING, true, + HtmlStyle.title, packageLabel); + tHeading.addContent(getSpace()); + Content packageHead = new StringContent(heading); + tHeading.addContent(packageHead); + div.addContent(tHeading); + addDeprecationInfo(div); + if (!utils.getBody(packageElement).isEmpty() && !configuration.nocomment) { + HtmlTree docSummaryDiv = new HtmlTree(HtmlTag.DIV); + docSummaryDiv.addStyle(HtmlStyle.docSummary); + addSummaryComment(packageElement, docSummaryDiv); + div.addContent(docSummaryDiv); + Content space = getSpace(); + Content descLink = getHyperLink(getDocLink( + SectionName.PACKAGE_DESCRIPTION), + descriptionLabel, "", ""); + Content descPara = new HtmlTree(HtmlTag.P, seeLabel, space, descLink); + div.addContent(descPara); + } + if (configuration.allowTag(HtmlTag.MAIN)) { + mainTree.addContent(div); + } else { + bodyTree.addContent(div); + } + return bodyTree; + } + + /** + * {@inheritDoc} + */ + public Content getContentHeader() { + HtmlTree div = new HtmlTree(HtmlTag.DIV); + div.addStyle(HtmlStyle.contentContainer); + return div; + } + + /** + * Add the package deprecation information to the documentation tree. + * + * @param div the content tree to which the deprecation information will be added + */ + public void addDeprecationInfo(Content div) { + List deprs = utils.getBlockTags(packageElement, DocTree.Kind.DEPRECATED); + if (utils.isDeprecated(packageElement)) { + CommentHelper ch = utils.getCommentHelper(packageElement); + HtmlTree deprDiv = new HtmlTree(HtmlTag.DIV); + deprDiv.addStyle(HtmlStyle.deprecatedContent); + Content deprPhrase = HtmlTree.SPAN(HtmlStyle.deprecatedLabel, deprecatedPhrase); + deprDiv.addContent(deprPhrase); + if (!deprs.isEmpty()) { + List commentTags = ch.getDescription(configuration, deprs.get(0)); + if (!commentTags.isEmpty()) { + addInlineDeprecatedComment(packageElement, deprs.get(0), deprDiv); + } + } + div.addContent(deprDiv); + } + } + + /** + * {@inheritDoc} + */ + public Content getSummaryHeader() { + HtmlTree ul = new HtmlTree(HtmlTag.UL); + ul.addStyle(HtmlStyle.blockList); + return ul; + } + + /** + * {@inheritDoc} + */ + public void addClassesSummary(SortedSet classes, String label, + String tableSummary, List tableHeader, Content summaryContentTree) { + if(!classes.isEmpty()) { + Content caption = getTableCaption(new RawHtml(label)); + Content table = (configuration.isOutputHtml5()) + ? HtmlTree.TABLE(HtmlStyle.typeSummary, caption) + : HtmlTree.TABLE(HtmlStyle.typeSummary, tableSummary, caption); + table.addContent(getSummaryTableHeader(tableHeader, "col")); + Content tbody = new HtmlTree(HtmlTag.TBODY); + boolean altColor = false; + for (TypeElement klass : classes) { + altColor = !altColor; + if (!utils.isCoreClass(klass) || + !configuration.isGeneratedDoc(klass)) { + continue; + } + Content classContent = getLink(new LinkInfoImpl( + configuration, LinkInfoImpl.Kind.PACKAGE, klass)); + Content tdClass = HtmlTree.TD(HtmlStyle.colFirst, classContent); + HtmlTree tr = HtmlTree.TR(tdClass); + tr.addStyle(altColor ? HtmlStyle.altColor : HtmlStyle.rowColor); + + HtmlTree tdClassDescription = new HtmlTree(HtmlTag.TD); + tdClassDescription.addStyle(HtmlStyle.colLast); + if (utils.isDeprecated(klass)) { + tdClassDescription.addContent(deprecatedLabel); + List tags = utils.getDeprecatedTrees(klass); + if (!tags.isEmpty()) { + addSummaryDeprecatedComment(klass, tags.get(0), tdClassDescription); + } + } else { + addSummaryComment(klass, tdClassDescription); + } + tr.addContent(tdClassDescription); + tbody.addContent(tr); + } + table.addContent(tbody); + Content li = HtmlTree.LI(HtmlStyle.blockList, table); + summaryContentTree.addContent(li); + } + } + + /** + * {@inheritDoc} + */ + public void addPackageDescription(Content packageContentTree) { + if (!utils.getBody(packageElement).isEmpty()) { + packageContentTree.addContent( + getMarkerAnchor(SectionName.PACKAGE_DESCRIPTION)); + Content h2Content = new StringContent( + configuration.getText("doclet.Package_Description", + packageElement.isUnnamed() ? "" : utils.getPackageName(packageElement))); + Content heading = HtmlTree.HEADING(HtmlConstants.PACKAGE_HEADING, true, h2Content); + if (configuration.allowTag(HtmlTag.SECTION)) { + sectionTree.addContent(heading); + addInlineComment(packageElement, sectionTree); + } else { + packageContentTree.addContent(heading); + addInlineComment(packageElement, packageContentTree); + } + } + } + + /** + * {@inheritDoc} + */ + public void addPackageTags(Content packageContentTree) { + Content htmlTree = (configuration.allowTag(HtmlTag.SECTION)) + ? sectionTree + : packageContentTree; + addTagsInfo(packageElement, htmlTree); + } + + /** + * {@inheritDoc} + */ + public void addPackageContent(Content contentTree, Content packageContentTree) { + if (configuration.allowTag(HtmlTag.MAIN)) { + packageContentTree.addContent(sectionTree); + mainTree.addContent(packageContentTree); + contentTree.addContent(mainTree); + } else { + contentTree.addContent(packageContentTree); + } + } + + /** + * {@inheritDoc} + */ + public void addPackageFooter(Content contentTree) { + Content htmlTree = (configuration.allowTag(HtmlTag.FOOTER)) + ? HtmlTree.FOOTER() + : contentTree; + addNavLinks(false, htmlTree); + addBottom(htmlTree); + if (configuration.allowTag(HtmlTag.FOOTER)) { + contentTree.addContent(htmlTree); + } + } + + /** + * {@inheritDoc} + */ + public void printDocument(Content contentTree) throws IOException { + printHtmlDocument(configuration.metakeywords.getMetaKeywords(packageElement), + true, contentTree); + } + + /** + * Get "Use" link for this pacakge in the navigation bar. + * + * @return a content tree for the class use link + */ + protected Content getNavLinkClassUse() { + Content useLink = getHyperLink(DocPaths.PACKAGE_USE, + useLabel, "", ""); + Content li = HtmlTree.LI(useLink); + return li; + } + + /** + * Get "PREV PACKAGE" link in the navigation bar. + * + * @return a content tree for the previous link + */ + public Content getNavLinkPrevious() { + Content li; + if (prev == null) { + li = HtmlTree.LI(prevpackageLabel); + } else { + DocPath path = DocPath.relativePath(packageElement, prev); + li = HtmlTree.LI(getHyperLink(path.resolve(DocPaths.PACKAGE_SUMMARY), + prevpackageLabel, "", "")); + } + return li; + } + + /** + * Get "NEXT PACKAGE" link in the navigation bar. + * + * @return a content tree for the next link + */ + public Content getNavLinkNext() { + Content li; + if (next == null) { + li = HtmlTree.LI(nextpackageLabel); + } else { + DocPath path = DocPath.relativePath(packageElement, next); + li = HtmlTree.LI(getHyperLink(path.resolve(DocPaths.PACKAGE_SUMMARY), + nextpackageLabel, "", "")); + } + return li; + } + + /** + * Get "Tree" link in the navigation bar. This will be link to the package + * tree file. + * + * @return a content tree for the tree link + */ + protected Content getNavLinkTree() { + Content useLink = getHyperLink(DocPaths.PACKAGE_TREE, + treeLabel, "", ""); + Content li = HtmlTree.LI(useLink); + return li; + } + + /** + * Highlight "Package" in the navigation bar, as this is the package page. + * + * @return a content tree for the package link + */ + protected Content getNavLinkPackage() { + Content li = HtmlTree.LI(HtmlStyle.navBarCell1Rev, packageLabel); + return li; + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PropertyWriterImpl.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PropertyWriterImpl.java new file mode 100644 index 00000000000..3a1eebc23cf --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PropertyWriterImpl.java @@ -0,0 +1,369 @@ +/* + * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; + +import java.util.Arrays; +import java.util.List; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; + +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.MemberSummaryWriter; +import jdk.javadoc.internal.doclets.toolkit.PropertyWriter; + + +/** + * Writes property documentation in HTML format. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Robert Field + * @author Atul M Dambalkar + * @author Jamie Ho (rewrite) + * @author Bhavesh Patel (Modified) + */ +public class PropertyWriterImpl extends AbstractMemberWriter + implements PropertyWriter, MemberSummaryWriter { + + public PropertyWriterImpl(SubWriterHolderWriter writer, TypeElement typeElement) { + super(writer, typeElement); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getMemberSummaryHeader(TypeElement typeElement, + Content memberSummaryTree) { + memberSummaryTree.addContent(HtmlConstants.START_OF_PROPERTY_SUMMARY); + Content memberTree = writer.getMemberTreeHeader(); + writer.addSummaryHeader(this, typeElement, memberTree); + return memberTree; + } + + /** + * {@inheritDoc} + */ + public void addMemberTree(Content memberSummaryTree, Content memberTree) { + writer.addMemberTree(memberSummaryTree, memberTree); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getPropertyDetailsTreeHeader(TypeElement typeElement, + Content memberDetailsTree) { + memberDetailsTree.addContent(HtmlConstants.START_OF_PROPERTY_DETAILS); + Content propertyDetailsTree = writer.getMemberTreeHeader(); + propertyDetailsTree.addContent(writer.getMarkerAnchor( + SectionName.PROPERTY_DETAIL)); + Content heading = HtmlTree.HEADING(HtmlConstants.DETAILS_HEADING, + writer.propertyDetailsLabel); + propertyDetailsTree.addContent(heading); + return propertyDetailsTree; + } + + /** + * {@inheritDoc} + */ + @Override + public Content getPropertyDocTreeHeader(ExecutableElement property, + Content propertyDetailsTree) { + propertyDetailsTree.addContent( + writer.getMarkerAnchor(name(property))); + Content propertyDocTree = writer.getMemberTreeHeader(); + Content heading = new HtmlTree(HtmlConstants.MEMBER_HEADING); + heading.addContent(utils.getPropertyLabel(name(property))); + propertyDocTree.addContent(heading); + return propertyDocTree; + } + + /** + * {@inheritDoc} + */ + @Override + public Content getSignature(ExecutableElement property) { + Content pre = new HtmlTree(HtmlTag.PRE); + writer.addAnnotationInfo(property, pre); + addModifiers(property, pre); + Content propertylink = writer.getLink(new LinkInfoImpl( + configuration, LinkInfoImpl.Kind.MEMBER, + utils.getReturnType(property))); + pre.addContent(propertylink); + pre.addContent(" "); + if (configuration.linksource) { + Content propertyName = new StringContent(name(property)); + writer.addSrcLink(property, propertyName, pre); + } else { + addName(name(property), pre); + } + return pre; + } + + /** + * {@inheritDoc} + */ + @Override + public void addDeprecated(ExecutableElement property, Content propertyDocTree) { + } + + /** + * {@inheritDoc} + */ + @Override + public void addComments(ExecutableElement property, Content propertyDocTree) { + TypeElement holder = (TypeElement)property.getEnclosingElement(); + if (!utils.getBody(property).isEmpty()) { + if (holder.equals(typeElement) || + (!utils.isPublic(holder) || utils.isLinkable(holder))) { + writer.addInlineComment(property, propertyDocTree); + } else { + Content link = + writer.getDocLink(LinkInfoImpl.Kind.PROPERTY_COPY, + holder, property, + utils.isIncluded(holder) + ? holder.toString() : utils.getFullyQualifiedName(holder), + false); + Content codeLink = HtmlTree.CODE(link); + Content descfrmLabel = HtmlTree.SPAN(HtmlStyle.descfrmTypeLabel, + utils.isClass(holder) + ? writer.descfrmClassLabel + : writer.descfrmInterfaceLabel); + descfrmLabel.addContent(writer.getSpace()); + descfrmLabel.addContent(codeLink); + propertyDocTree.addContent(HtmlTree.DIV(HtmlStyle.block, descfrmLabel)); + writer.addInlineComment(property, propertyDocTree); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void addTags(ExecutableElement property, Content propertyDocTree) { + writer.addTagsInfo(property, propertyDocTree); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getPropertyDetails(Content propertyDetailsTree) { + if (configuration.allowTag(HtmlTag.SECTION)) { + HtmlTree htmlTree = HtmlTree.SECTION(getMemberTree(propertyDetailsTree)); + return htmlTree; + } + return getMemberTree(propertyDetailsTree); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getPropertyDoc(Content propertyDocTree, + boolean isLastContent) { + return getMemberTree(propertyDocTree, isLastContent); + } + + /** + * Close the writer. + */ + @Override + public void close() throws IOException { + writer.close(); + } + + /** + * {@inheritDoc} + */ + @Override + public void addSummaryLabel(Content memberTree) { + Content label = HtmlTree.HEADING(HtmlConstants.SUMMARY_HEADING, + writer.getResource("doclet.Property_Summary")); + memberTree.addContent(label); + } + + /** + * {@inheritDoc} + */ + @Override + public String getTableSummary() { + return configuration.getText("doclet.Member_Table_Summary", + configuration.getText("doclet.Property_Summary"), + configuration.getText("doclet.properties")); + } + + /** + * {@inheritDoc} + */ + @Override + public Content getCaption() { + return configuration.getResource("doclet.Properties"); + } + + /** + * {@inheritDoc} + */ + @Override + public List getSummaryTableHeader(Element member) { + List header = Arrays.asList(configuration.getText("doclet.Type"), + configuration.getText("doclet.0_and_1", + configuration.getText("doclet.Property"), + configuration.getText("doclet.Description"))); + return header; + } + + /** + * {@inheritDoc} + */ + @Override + public void addSummaryAnchor(TypeElement typeElement, Content memberTree) { + memberTree.addContent(writer.getMarkerAnchor( + SectionName.PROPERTY_SUMMARY)); + } + + /** + * {@inheritDoc} + */ + @Override + public void addInheritedSummaryAnchor(TypeElement typeElement, Content inheritedTree) { + inheritedTree.addContent(writer.getMarkerAnchor( + SectionName.PROPERTIES_INHERITANCE, + configuration.getClassName(typeElement))); + } + + /** + * {@inheritDoc} + */ + @Override + public void addInheritedSummaryLabel(TypeElement typeElement, Content inheritedTree) { + Content classLink = writer.getPreQualifiedClassLink( + LinkInfoImpl.Kind.MEMBER, typeElement, false); + Content label = new StringContent( + utils.isClass(typeElement) + ? configuration.getText("doclet.Properties_Inherited_From_Class") + : configuration.getText("doclet.Properties_Inherited_From_Interface")); + Content labelHeading = HtmlTree.HEADING(HtmlConstants.INHERITED_SUMMARY_HEADING, + label); + labelHeading.addContent(writer.getSpace()); + labelHeading.addContent(classLink); + inheritedTree.addContent(labelHeading); + } + + /** + * {@inheritDoc} + */ + @Override + protected void addSummaryLink(LinkInfoImpl.Kind context, TypeElement typeElement, Element member, + Content tdSummary) { + Content memberLink = HtmlTree.SPAN(HtmlStyle.memberNameLink, + writer.getDocLink(context, typeElement, + member, + utils.getPropertyLabel(name(member)), + false, + true)); + + Content code = HtmlTree.CODE(memberLink); + tdSummary.addContent(code); + } + + /** + * {@inheritDoc} + */ + @Override + protected void addInheritedSummaryLink(TypeElement typeElement, Element member, Content linksTree) { + String mname = name(member); + Content content = writer.getDocLink(LinkInfoImpl.Kind.MEMBER, typeElement, member, + utils.isProperty(mname) ? utils.getPropertyName(mname) : mname, + false, true); + linksTree.addContent(content); + } + + /** + * {@inheritDoc} + */ + @Override + protected void addSummaryType(Element member, Content tdSummaryType) { + addModifierAndType(member, utils.getReturnType((ExecutableElement)member), tdSummaryType); + } + + /** + * {@inheritDoc} + */ + @Override + protected Content getDeprecatedLink(Element member) { + return writer.getDocLink(LinkInfoImpl.Kind.MEMBER, member, + utils.getFullyQualifiedName(member)); + } + + /** + * {@inheritDoc} + */ + @Override + protected Content getNavSummaryLink(TypeElement typeElement, boolean link) { + if (link) { + if (typeElement == null) { + return writer.getHyperLink( + SectionName.PROPERTY_SUMMARY, + writer.getResource("doclet.navProperty")); + } else { + return writer.getHyperLink( + SectionName.PROPERTIES_INHERITANCE, + configuration.getClassName(typeElement), writer.getResource("doclet.navProperty")); + } + } else { + return writer.getResource("doclet.navProperty"); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void addNavDetailLink(boolean link, Content liNav) { + if (link) { + liNav.addContent(writer.getHyperLink( + SectionName.PROPERTY_DETAIL, + writer.getResource("doclet.navProperty"))); + } else { + liNav.addContent(writer.getResource("doclet.navProperty")); + } + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SearchIndexItem.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SearchIndexItem.java new file mode 100644 index 00000000000..d260a9b5538 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SearchIndexItem.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +/** + * Index item for search. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class SearchIndexItem { + + private String label = ""; + private String url = ""; + private String category = ""; + private String containingPackage = ""; + private String containingClass = ""; + private String holder = ""; + private String description = ""; + + public void setLabel(String l) { + label = l; + } + + public String getLabel() { + return label; + } + + public void setUrl(String u) { + url = u; + } + + public String getUrl() { + return url; + } + + public void setContainingPackage(String p) { + containingPackage = p; + } + + public void setContainingClass(String c) { + containingClass = c; + } + + public void setCategory(String c) { + category = c; + } + + public void setHolder(String h) { + holder = h; + } + + public String getHolder() { + return holder; + } + + public void setDescription(String d) { + description = d; + } + + public String getDescription() { + return description; + } + + public String toString() { + StringBuilder item = new StringBuilder(""); + if (category.equals("Packages")) { + item.append("{") + .append("\"l\":\"").append(label).append("\"") + .append("}"); + } else if (category.equals("Types")) { + item.append("{") + .append("\"p\":\"").append(containingPackage).append("\",") + .append("\"l\":\"").append(label).append("\"") + .append("}"); + } else if (category.equals("Members")) { + item.append("{") + .append("\"p\":\"").append(containingPackage).append("\",") + .append("\"c\":\"").append(containingClass).append("\",") + .append("\"l\":\"").append(label).append("\""); + if (!url.equals("")) { + item.append(",\"url\":\"").append(url).append("\""); + } + item.append("}"); + } else { + item.append("{") + .append("\"l\":\"").append(label).append("\",") + .append("\"h\":\"").append(holder).append("\","); + if (!description.equals("")) { + item.append("\"d\":\"").append(description).append("\","); + } + item.append("\"u\":\"").append(url).append("\"") + .append("}"); + } + return item.toString(); + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SectionName.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SectionName.java new file mode 100644 index 00000000000..a9ff3e50311 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SectionName.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +/** + * Enum representing various section names of generated API documentation. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Bhavesh Patel + */ +public enum SectionName { + + ANNOTATION_TYPE_ELEMENT_DETAIL("annotation.type.element.detail"), + ANNOTATION_TYPE_FIELD_DETAIL("annotation.type.field.detail"), + ANNOTATION_TYPE_FIELD_SUMMARY("annotation.type.field.summary"), + ANNOTATION_TYPE_OPTIONAL_ELEMENT_SUMMARY("annotation.type.optional.element.summary"), + ANNOTATION_TYPE_REQUIRED_ELEMENT_SUMMARY("annotation.type.required.element.summary"), + CONSTRUCTOR_DETAIL("constructor.detail"), + CONSTRUCTOR_SUMMARY("constructor.summary"), + ENUM_CONSTANT_DETAIL("enum.constant.detail"), + ENUM_CONSTANTS_INHERITANCE("enum.constants.inherited.from.class."), + ENUM_CONSTANT_SUMMARY("enum.constant.summary"), + FIELD_DETAIL("field.detail"), + FIELDS_INHERITANCE("fields.inherited.from.class."), + FIELD_SUMMARY("field.summary"), + METHOD_DETAIL("method.detail"), + METHODS_INHERITANCE("methods.inherited.from.class."), + METHOD_SUMMARY("method.summary"), + NAVBAR_BOTTOM("navbar.bottom"), + NAVBAR_BOTTOM_FIRSTROW("navbar.bottom.firstrow"), + NAVBAR_TOP("navbar.top"), + NAVBAR_TOP_FIRSTROW("navbar.top.firstrow"), + NESTED_CLASSES_INHERITANCE("nested.classes.inherited.from.class."), + NESTED_CLASS_SUMMARY("nested.class.summary"), + OVERVIEW_DESCRIPTION("overview.description"), + PACKAGE_DESCRIPTION("package.description"), + PROPERTY_DETAIL("property.detail"), + PROPERTIES_INHERITANCE("properties.inherited.from.class."), + PROPERTY_SUMMARY("property.summary"), + SKIP_NAVBAR_BOTTOM("skip.navbar.bottom"), + SKIP_NAVBAR_TOP("skip.navbar.top"), + UNNAMED_PACKAGE_ANCHOR("unnamed.package"); + + private final String value; + + SectionName(String sName) { + this.value = sName; + } + + public String getName() { + return this.value; + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SerializedFormWriterImpl.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SerializedFormWriterImpl.java new file mode 100644 index 00000000000..764810f3902 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SerializedFormWriterImpl.java @@ -0,0 +1,301 @@ +/* + * Copyright (c) 1998, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; +import java.util.*; + +import javax.lang.model.element.TypeElement; + +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.SerializedFormWriter; +import jdk.javadoc.internal.doclets.toolkit.SerializedFormWriter.SerialFieldWriter; +import jdk.javadoc.internal.doclets.toolkit.SerializedFormWriter.SerialMethodWriter; +import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; +import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; + +/** + * Generate the Serialized Form Information Page. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Atul M Dambalkar + */ +public class SerializedFormWriterImpl extends SubWriterHolderWriter + implements SerializedFormWriter { + + Set visibleClasses; + + /** + * HTML tree for main tag. + */ + private HtmlTree mainTree = HtmlTree.MAIN(); + + /** + * @param configuration the configuration data for the doclet + * @throws IOException + * @throws DocletAbortException + */ + public SerializedFormWriterImpl(ConfigurationImpl configuration) + throws IOException { + super(configuration, DocPaths.SERIALIZED_FORM); + visibleClasses = configuration.root.getIncludedClasses(); + } + + /** + * Get the given header. + * + * @param header the header to write + * @return the body content tree + */ + public Content getHeader(String header) { + HtmlTree bodyTree = getBody(true, getWindowTitle(header)); + HtmlTree htmlTree = (configuration.allowTag(HtmlTag.HEADER)) + ? HtmlTree.HEADER() + : bodyTree; + addTop(htmlTree); + addNavLinks(true, htmlTree); + if (configuration.allowTag(HtmlTag.HEADER)) { + bodyTree.addContent(htmlTree); + } + Content h1Content = new StringContent(header); + Content heading = HtmlTree.HEADING(HtmlConstants.TITLE_HEADING, true, + HtmlStyle.title, h1Content); + Content div = HtmlTree.DIV(HtmlStyle.header, heading); + if (configuration.allowTag(HtmlTag.MAIN)) { + mainTree.addContent(div); + } else { + bodyTree.addContent(div); + } + return bodyTree; + } + + /** + * Get the serialized form summaries header. + * + * @return the serialized form summary header tree + */ + public Content getSerializedSummariesHeader() { + HtmlTree ul = new HtmlTree(HtmlTag.UL); + ul.addStyle(HtmlStyle.blockList); + return ul; + } + + /** + * Get the package serialized form header. + * + * @return the package serialized form header tree + */ + public Content getPackageSerializedHeader() { + HtmlTree htmlTree; + if (configuration.allowTag(HtmlTag.SECTION)) { + htmlTree = HtmlTree.SECTION(); + } else { + htmlTree = new HtmlTree(HtmlTag.LI); + htmlTree.addStyle(HtmlStyle.blockList); + } + return htmlTree; + } + + /** + * Get the given package header. + * + * @param packageName the package header to write + * @return a content tree for the package header + */ + public Content getPackageHeader(String packageName) { + Content heading = HtmlTree.HEADING(HtmlConstants.PACKAGE_HEADING, true, + packageLabel); + heading.addContent(getSpace()); + heading.addContent(packageName); + return heading; + } + + /** + * Get the serialized class header. + * + * @return a content tree for the serialized class header + */ + public Content getClassSerializedHeader() { + HtmlTree ul = new HtmlTree(HtmlTag.UL); + ul.addStyle(HtmlStyle.blockList); + return ul; + } + + /** + * Checks if a class is generated and is visible. + * + * @param typeElement the class being processed. + * @return true if the class, that is being processed, is generated and is visible. + */ + public boolean isVisibleClass(TypeElement typeElement) { + return visibleClasses.contains(typeElement) && configuration.isGeneratedDoc(typeElement); + } + + /** + * Get the serializable class heading. + * + * @param typeElement the class being processed + * @return a content tree for the class header + */ + public Content getClassHeader(TypeElement typeElement) { + Content classLink = (isVisibleClass(typeElement)) + ? getLink(new LinkInfoImpl(configuration, LinkInfoImpl.Kind.DEFAULT, typeElement) + .label(configuration.getClassName(typeElement))) + : new StringContent(utils.getFullyQualifiedName(typeElement)); + Content li = HtmlTree.LI(HtmlStyle.blockList, getMarkerAnchor( + utils.getFullyQualifiedName(typeElement))); + Content superClassLink = typeElement.getSuperclass() != null + ? getLink(new LinkInfoImpl(configuration, LinkInfoImpl.Kind.SERIALIZED_FORM, + typeElement.getSuperclass())) + : null; + + //Print the heading. + Content className = superClassLink == null ? + configuration.getResource( + "doclet.Class_0_implements_serializable", classLink) : + configuration.getResource( + "doclet.Class_0_extends_implements_serializable", classLink, + superClassLink); + li.addContent(HtmlTree.HEADING(HtmlConstants.SERIALIZED_MEMBER_HEADING, + className)); + return li; + } + + /** + * Get the serial UID info header. + * + * @return a content tree for the serial uid info header + */ + public Content getSerialUIDInfoHeader() { + HtmlTree dl = new HtmlTree(HtmlTag.DL); + dl.addStyle(HtmlStyle.nameValue); + return dl; + } + + /** + * Adds the serial UID info. + * + * @param header the header that will show up before the UID. + * @param serialUID the serial UID to print. + * @param serialUidTree the serial UID content tree to which the serial UID + * content will be added + */ + public void addSerialUIDInfo(String header, String serialUID, + Content serialUidTree) { + Content headerContent = new StringContent(header); + serialUidTree.addContent(HtmlTree.DT(headerContent)); + Content serialContent = new StringContent(serialUID); + serialUidTree.addContent(HtmlTree.DD(serialContent)); + } + + /** + * Get the class serialize content header. + * + * @return a content tree for the class serialize content header + */ + public Content getClassContentHeader() { + HtmlTree ul = new HtmlTree(HtmlTag.UL); + ul.addStyle(HtmlStyle.blockList); + return ul; + } + + /** + * Get the serialized content tree section. + * + * @param serializedTreeContent the serialized content tree to be added + * @return a div content tree + */ + public Content getSerializedContent(Content serializedTreeContent) { + HtmlTree divContent = HtmlTree.DIV(HtmlStyle.serializedFormContainer, + serializedTreeContent); + if (configuration.allowTag(HtmlTag.MAIN)) { + mainTree.addContent(divContent); + return mainTree; + } else { + return divContent; + } + } + + /** + * {@inheritDoc} + */ + public void addPackageSerializedTree(Content serializedSummariesTree, + Content packageSerializedTree) { + serializedSummariesTree.addContent((configuration.allowTag(HtmlTag.SECTION)) + ? HtmlTree.LI(HtmlStyle.blockList, packageSerializedTree) + : packageSerializedTree); + } + + /** + * Add the footer. + * + * @param serializedTree the serialized tree to be added + */ + public void addFooter(Content serializedTree) { + Content htmlTree = (configuration.allowTag(HtmlTag.FOOTER)) + ? HtmlTree.FOOTER() + : serializedTree; + addNavLinks(false, htmlTree); + addBottom(htmlTree); + if (configuration.allowTag(HtmlTag.FOOTER)) { + serializedTree.addContent(htmlTree); + } + } + + /** + * {@inheritDoc} + */ + public void printDocument(Content serializedTree) throws IOException { + printHtmlDocument(null, true, serializedTree); + } + + /** + * Return an instance of a SerialFieldWriter. + * + * @return an instance of a SerialFieldWriter. + */ + public SerialFieldWriter getSerialFieldWriter(TypeElement typeElement) { + return new HtmlSerialFieldWriter(this, typeElement); + } + + /** + * Return an instance of a SerialMethodWriter. + * + * @return an instance of a SerialMethodWriter. + */ + public SerialMethodWriter getSerialMethodWriter(TypeElement typeElement) { + return new HtmlSerialMethodWriter(this, typeElement); + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SingleIndexWriter.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SingleIndexWriter.java new file mode 100644 index 00000000000..81189d6ee61 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SingleIndexWriter.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 1998, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; +import java.util.*; + +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; +import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; +import jdk.javadoc.internal.doclets.toolkit.util.IndexBuilder; + + +/** + * Generate only one index file for all the Member Names with Indexing in + * Unicode Order. The name of the generated file is "index-all.html" and it is + * generated in current or the destination directory. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @see java.lang.Character + * @author Atul M Dambalkar + * @author Bhavesh Patel (Modified) + */ +public class SingleIndexWriter extends AbstractIndexWriter { + + private Set elements; + + /** + * Construct the SingleIndexWriter with filename "index-all.html" and the + * {@link IndexBuilder} + * + * @param filename Name of the index file to be generated. + * @param indexbuilder Unicode based Index from {@link IndexBuilder} + */ + public SingleIndexWriter(ConfigurationImpl configuration, + DocPath filename, + IndexBuilder indexbuilder) throws IOException { + super(configuration, filename, indexbuilder); + } + + /** + * Generate single index file, for all Unicode characters. + * + * @param indexbuilder IndexBuilder built by {@link IndexBuilder} + * @throws DocletAbortException + */ + public static void generate(ConfigurationImpl configuration, + IndexBuilder indexbuilder) { + SingleIndexWriter indexgen; + DocPath filename = DocPaths.INDEX_ALL; + try { + indexgen = new SingleIndexWriter(configuration, + filename, indexbuilder); + indexgen.generateIndexFile(); + indexgen.close(); + } catch (IOException exc) { + configuration.standardmessage.error( + "doclet.exception_encountered", + exc.toString(), filename); + throw new DocletAbortException(exc); + } + } + + /** + * Generate the contents of each index file, with Header, Footer, + * Member Field, Method and Constructor Description. + */ + protected void generateIndexFile() throws IOException { + String title = configuration.getText("doclet.Window_Single_Index"); + HtmlTree body = getBody(true, getWindowTitle(title)); + HtmlTree htmlTree = (configuration.allowTag(HtmlTag.HEADER)) + ? HtmlTree.HEADER() + : body; + addTop(htmlTree); + addNavLinks(true, htmlTree); + if (configuration.allowTag(HtmlTag.HEADER)) { + body.addContent(htmlTree); + } + HtmlTree divTree = new HtmlTree(HtmlTag.DIV); + divTree.addStyle(HtmlStyle.contentContainer); + elements = new TreeSet<>(indexbuilder.getIndexMap().keySet()); + elements.addAll(configuration.tagSearchIndexKeys); + addLinksForIndexes(divTree); + for (Character unicode : elements) { + if (configuration.tagSearchIndexMap.get(unicode) == null) { + addContents(unicode, indexbuilder.getMemberList(unicode), divTree); + } else if (indexbuilder.getMemberList(unicode) == null) { + addSearchContents(unicode, configuration.tagSearchIndexMap.get(unicode), divTree); + } else { + addContents(unicode, indexbuilder.getMemberList(unicode), + configuration.tagSearchIndexMap.get(unicode), divTree); + } + } + addLinksForIndexes(divTree); + body.addContent((configuration.allowTag(HtmlTag.MAIN)) + ? HtmlTree.MAIN(divTree) + : divTree); + if (configuration.allowTag(HtmlTag.FOOTER)) { + htmlTree = HtmlTree.FOOTER(); + } + addNavLinks(false, htmlTree); + addBottom(htmlTree); + if (configuration.allowTag(HtmlTag.FOOTER)) { + body.addContent(htmlTree); + } + createSearchIndexFiles(); + printHtmlDocument(null, true, body); + } + + /** + * Add links for all the Index Files per unicode character. + * + * @param contentTree the content tree to which the links for indexes will be added + */ + protected void addLinksForIndexes(Content contentTree) { + for (Object ch : elements) { + String unicode = ch.toString(); + contentTree.addContent( + getHyperLink(getNameForIndex(unicode), + new StringContent(unicode))); + contentTree.addContent(getSpace()); + } + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SourceToHTMLConverter.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SourceToHTMLConverter.java new file mode 100644 index 00000000000..0396188d8be --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SourceToHTMLConverter.java @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2001, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; + +import javax.lang.model.element.Element; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.tools.FileObject; + +import jdk.javadoc.doclet.DocletEnvironment; +import jdk.javadoc.internal.doclets.formats.html.markup.DocType; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlDocument; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.DocFile; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; +import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; +import jdk.javadoc.internal.doclets.toolkit.util.DocletConstants; +import jdk.javadoc.internal.doclets.toolkit.util.Utils; + +/** + * Converts Java Source Code to HTML. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Jamie Ho + * @author Bhavesh Patel (Modified) + */ +public class SourceToHTMLConverter { + + /** + * The number of trailing blank lines at the end of the page. + * This is inserted so that anchors at the bottom of small pages + * can be reached. + */ + private static final int NUM_BLANK_LINES = 60; + + /** + * New line to be added to the documentation. + */ + private static final String NEW_LINE = DocletConstants.NL; + + private final ConfigurationImpl configuration; + private final Utils utils; + + private final DocletEnvironment rootDoc; + + private DocPath outputdir; + + /** + * Relative path from the documentation root to the file that is being + * generated. + */ + private DocPath relativePath = DocPath.empty; + + private SourceToHTMLConverter(ConfigurationImpl configuration, DocletEnvironment rd, + DocPath outputdir) { + this.configuration = configuration; + this.utils = configuration.utils; + this.rootDoc = rd; + this.outputdir = outputdir; + } + + /** + * Translate the TypeElements in the given DocletEnvironment to HTML representation. + * + * @param configuration the configuration. + * @param root the DocletEnvironment to convert. + * @param outputdir the name of the directory to output to. + */ + public static void convertRoot(ConfigurationImpl configuration, DocletEnvironment root, + DocPath outputdir) { + new SourceToHTMLConverter(configuration, root, outputdir).generate(); + } + + void generate() { + if (rootDoc == null || outputdir == null) { + return; + } + for (PackageElement pkg : utils.getSpecifiedPackages()) { + // If -nodeprecated option is set and the package is marked as deprecated, + // do not convert the package files to HTML. + if (!(configuration.nodeprecated && utils.isDeprecated(pkg))) + convertPackage(pkg, outputdir); + } + for (TypeElement te : utils.getSpecifiedClasses()) { + // If -nodeprecated option is set and the class is marked as deprecated + // or the containing package is deprecated, do not convert the + // package files to HTML. + if (!(configuration.nodeprecated && + (utils.isDeprecated(te) || utils.isDeprecated(utils.containingPackage(te))))) + convertClass(te, outputdir); + } + } + + /** + * Convert the Classes in the given Package to an HTML. + * + * @param pkg the Package to convert. + * @param outputdir the name of the directory to output to. + */ + public void convertPackage(PackageElement pkg, DocPath outputdir) { + if (pkg == null) { + return; + } + for (Element te : utils.getAllClasses(pkg)) { + // If -nodeprecated option is set and the class is marked as deprecated, + // do not convert the package files to HTML. We do not check for + // containing package deprecation since it is already check in + // the calling method above. + if (!(configuration.nodeprecated && utils.isDeprecated(te))) + convertClass((TypeElement)te, outputdir); + } + } + + /** + * Convert the given Class to an HTML. + * + * @param te the class to convert. + * @param outputdir the name of the directory to output to. + */ + public void convertClass(TypeElement te, DocPath outputdir) { + if (te == null) { + return; + } + try { + FileObject fo = utils.getFileObject(te); + if (fo == null) + return; + Reader r = fo.openReader(true); + int lineno = 1; + String line; + relativePath = DocPaths.SOURCE_OUTPUT + .resolve(DocPath.forPackage(utils, te)) + .invert(); + Content body = getHeader(); + Content pre = new HtmlTree(HtmlTag.PRE); + try (LineNumberReader reader = new LineNumberReader(r)) { + while ((line = reader.readLine()) != null) { + addLineNo(pre, lineno); + addLine(pre, line, lineno); + lineno++; + } + } + addBlankLines(pre); + Content div = HtmlTree.DIV(HtmlStyle.sourceContainer, pre); + body.addContent((configuration.allowTag(HtmlTag.MAIN)) ? HtmlTree.MAIN(div) : div); + writeToFile(body, outputdir.resolve(DocPath.forClass(utils, te))); + } catch (IOException e) { + throw new DocletAbortException(e); + } + } + + /** + * Write the output to the file. + * + * @param body the documentation content to be written to the file. + * @param path the path for the file. + */ + private void writeToFile(Content body, DocPath path) throws IOException { + Content htmlDocType = configuration.isOutputHtml5() + ? DocType.HTML5 + : DocType.TRANSITIONAL; + Content head = new HtmlTree(HtmlTag.HEAD); + head.addContent(HtmlTree.TITLE(new StringContent( + configuration.getText("doclet.Window_Source_title")))); + head.addContent(getStyleSheetProperties()); + Content htmlTree = HtmlTree.HTML(configuration.getLocale().getLanguage(), + head, body); + Content htmlDocument = new HtmlDocument(htmlDocType, htmlTree); + configuration.message.notice("doclet.Generating_0", path.getPath()); + DocFile df = DocFile.createFileForOutput(configuration, path); + try (Writer w = df.openWriter()) { + htmlDocument.write(w, true); + } + + } + + /** + * Returns a link to the stylesheet file. + * + * @return an HtmlTree for the lINK tag which provides the stylesheet location + */ + public HtmlTree getStyleSheetProperties() { + String filename = configuration.stylesheetfile; + DocPath stylesheet; + if (filename.length() > 0) { + DocFile file = DocFile.createFileForInput(configuration, filename); + stylesheet = DocPath.create(file.getName()); + } else { + stylesheet = DocPaths.STYLESHEET; + } + DocPath p = relativePath.resolve(stylesheet); + HtmlTree link = HtmlTree.LINK("stylesheet", "text/css", p.getPath(), "Style"); + return link; + } + + /** + * Get the header. + * + * @return the header content for the HTML file + */ + private static Content getHeader() { + return new HtmlTree(HtmlTag.BODY); + } + + /** + * Add the line numbers for the source code. + * + * @param pre the content tree to which the line number will be added + * @param lineno The line number + */ + private static void addLineNo(Content pre, int lineno) { + HtmlTree span = new HtmlTree(HtmlTag.SPAN); + span.addStyle(HtmlStyle.sourceLineNo); + if (lineno < 10) { + span.addContent("00" + Integer.toString(lineno)); + } else if (lineno < 100) { + span.addContent("0" + Integer.toString(lineno)); + } else { + span.addContent(Integer.toString(lineno)); + } + pre.addContent(span); + } + + /** + * Add a line from source to the HTML file that is generated. + * + * @param pre the content tree to which the line will be added. + * @param line the string to format. + * @param currentLineNo the current number. + */ + private void addLine(Content pre, String line, int currentLineNo) { + if (line != null) { + Content anchor = HtmlTree.A(configuration.htmlVersion, + "line." + Integer.toString(currentLineNo), + new StringContent(utils.replaceTabs(line))); + pre.addContent(anchor); + pre.addContent(NEW_LINE); + } + } + + /** + * Add trailing blank lines at the end of the page. + * + * @param pre the content tree to which the blank lines will be added. + */ + private static void addBlankLines(Content pre) { + for (int i = 0; i < NUM_BLANK_LINES; i++) { + pre.addContent(NEW_LINE); + } + } + + /** + * Given a Doc, return an anchor name for it. + * + * @param d the Doc to check. + * @return the name of the anchor. + */ + public static String getAnchorName(Utils utils, Element e) { + return "line." + utils.getLineNumber(e); + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SplitIndexWriter.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SplitIndexWriter.java new file mode 100644 index 00000000000..f73407cd5e2 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SplitIndexWriter.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 1998, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; +import java.util.TreeSet; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; +import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; +import jdk.javadoc.internal.doclets.toolkit.util.IndexBuilder; + + +/** + * Generate Separate Index Files for all the member names with Indexing in + * Unicode Order. This will create "index-files" directory in the current or + * destination directory and will generate separate file for each unicode index. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @see java.lang.Character + * @author Atul M Dambalkar + * @author Bhavesh Patel (Modified) + */ +public class SplitIndexWriter extends AbstractIndexWriter { + + /** + * Previous unicode character index in the built index. + */ + protected int prev; + + /** + * Next unicode character in the built index. + */ + protected int next; + + private List indexElements; + + /** + * Construct the SplitIndexWriter. Uses path to this file and relative path + * from this file. + * + * @param path Path to the file which is getting generated. + * @param indexbuilder Unicode based Index from {@link IndexBuilder} + */ + public SplitIndexWriter(ConfigurationImpl configuration, + DocPath path, + IndexBuilder indexbuilder, + Collection elements, + int prev, int next) throws IOException { + super(configuration, path, indexbuilder); + this.indexElements = new ArrayList<>(elements); + this.prev = prev; + this.next = next; + } + + /** + * Generate separate index files, for each Unicode character, listing all + * the members starting with the particular unicode character. + * + * @param indexbuilder IndexBuilder built by {@link IndexBuilder} + * @throws DocletAbortException + */ + public static void generate(ConfigurationImpl configuration, + IndexBuilder indexbuilder) { + SplitIndexWriter indexgen; + DocPath filename = DocPath.empty; + DocPath path = DocPaths.INDEX_FILES; + try { + Set keys = new TreeSet<>(indexbuilder.getIndexMap().keySet()); + keys.addAll(configuration.tagSearchIndexKeys); + ListIterator li = new ArrayList<>(keys).listIterator(); + while (li.hasNext()) { + Object ch = li.next(); + filename = DocPaths.indexN(li.nextIndex()); + indexgen = new SplitIndexWriter(configuration, + path.resolve(filename), + indexbuilder, keys, li.previousIndex(), li.nextIndex()); + indexgen.generateIndexFile((Character) ch); + if (!li.hasNext()) { + indexgen.createSearchIndexFiles(); + } + indexgen.close(); + } + } catch (IOException exc) { + configuration.standardmessage.error( + "doclet.exception_encountered", + exc.toString(), filename.getPath()); + throw new DocletAbortException(exc); + } + } + + /** + * Generate the contents of each index file, with Header, Footer, + * Member Field, Method and Constructor Description. + * + * @param unicode Unicode character referring to the character for the + * index. + */ + protected void generateIndexFile(Character unicode) throws IOException { + String title = configuration.getText("doclet.Window_Split_Index", + unicode.toString()); + HtmlTree body = getBody(true, getWindowTitle(title)); + HtmlTree htmlTree = (configuration.allowTag(HtmlTag.HEADER)) + ? HtmlTree.HEADER() + : body; + addTop(htmlTree); + addNavLinks(true, htmlTree); + if (configuration.allowTag(HtmlTag.HEADER)) { + body.addContent(htmlTree); + } + HtmlTree divTree = new HtmlTree(HtmlTag.DIV); + divTree.addStyle(HtmlStyle.contentContainer); + addLinksForIndexes(divTree); + if (configuration.tagSearchIndexMap.get(unicode) == null) { + addContents(unicode, indexbuilder.getMemberList(unicode), divTree); + } else if (indexbuilder.getMemberList(unicode) == null) { + addSearchContents(unicode, configuration.tagSearchIndexMap.get(unicode), divTree); + } else { + addContents(unicode, indexbuilder.getMemberList(unicode), + configuration.tagSearchIndexMap.get(unicode), divTree); + } + addLinksForIndexes(divTree); + body.addContent((configuration.allowTag(HtmlTag.MAIN)) ? HtmlTree.MAIN(divTree) : divTree); + if (configuration.allowTag(HtmlTag.FOOTER)) { + htmlTree = HtmlTree.FOOTER(); + } + addNavLinks(false, htmlTree); + addBottom(htmlTree); + if (configuration.allowTag(HtmlTag.FOOTER)) { + body.addContent(htmlTree); + } + printHtmlDocument(null, true, body); + } + + /** + * Add links for all the Index Files per unicode character. + * + * @param contentTree the content tree to which the links for indexes will be added + */ + protected void addLinksForIndexes(Content contentTree) { + for (int i = 0; i < indexElements.size(); i++) { + int j = i + 1; + contentTree.addContent(getHyperLink(DocPaths.indexN(j), + new StringContent(indexElements.get(i).toString()))); + contentTree.addContent(getSpace()); + } + } + + /** + * Get link to the previous unicode character. + * + * @return a content tree for the link + */ + public Content getNavLinkPrevious() { + Content prevletterLabel = getResource("doclet.Prev_Letter"); + if (prev == -1) { + return HtmlTree.LI(prevletterLabel); + } + else { + Content prevLink = getHyperLink(DocPaths.indexN(prev), + prevletterLabel); + return HtmlTree.LI(prevLink); + } + } + + /** + * Get link to the next unicode character. + * + * @return a content tree for the link + */ + public Content getNavLinkNext() { + Content nextletterLabel = getResource("doclet.Next_Letter"); + if (next == -1) { + return HtmlTree.LI(nextletterLabel); + } + else { + Content nextLink = getHyperLink(DocPaths.indexN(next), + nextletterLabel); + return HtmlTree.LI(nextLink); + } + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SubWriterHolderWriter.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SubWriterHolderWriter.java new file mode 100644 index 00000000000..11888eb26db --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SubWriterHolderWriter.java @@ -0,0 +1,368 @@ +/* + * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.*; +import java.util.*; + +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; + +import com.sun.source.doctree.DocTree; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.MethodTypes; + +/** + * This abstract class exists to provide functionality needed in the + * the formatting of member information. Since AbstractSubWriter and its + * subclasses control this, they would be the logical place to put this. + * However, because each member type has its own subclass, subclassing + * can not be used effectively to change formatting. The concrete + * class subclass of this class can be subclassed to change formatting. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @see AbstractMemberWriter + * @see ClassWriterImpl + * + * @author Robert Field + * @author Atul M Dambalkar + * @author Bhavesh Patel (Modified) + */ +public abstract class SubWriterHolderWriter extends HtmlDocletWriter { + + /** + * The HTML tree for main tag. + */ + protected HtmlTree mainTree = HtmlTree.MAIN(); + + public SubWriterHolderWriter(ConfigurationImpl configuration, DocPath filename) + throws IOException { + super(configuration, filename); + } + + /** + * Add the summary header. + * + * @param mw the writer for the member being documented + * @param typeElement the te to be documented + * @param memberTree the content tree to which the summary header will be added + */ + public void addSummaryHeader(AbstractMemberWriter mw, TypeElement typeElement, + Content memberTree) { + mw.addSummaryAnchor(typeElement, memberTree); + mw.addSummaryLabel(memberTree); + } + + /** + * Get the summary table. + * + * @param mw the writer for the member being documented + * @param typeElement the te to be documented + * @param tableContents list of summary table contents + * @param showTabs true if the table needs to show tabs + * @return the content tree for the summary table + */ + public Content getSummaryTableTree(AbstractMemberWriter mw, TypeElement typeElement, + List tableContents, boolean showTabs) { + Content caption; + if (showTabs) { + caption = getTableCaption(mw.methodTypes); + generateMethodTypesScript(mw.typeMap, mw.methodTypes); + } + else { + caption = getTableCaption(mw.getCaption()); + } + Content table = (configuration.isOutputHtml5()) + ? HtmlTree.TABLE(HtmlStyle.memberSummary, caption) + : HtmlTree.TABLE(HtmlStyle.memberSummary, mw.getTableSummary(), caption); + table.addContent(getSummaryTableHeader(mw.getSummaryTableHeader(typeElement), "col")); + for (Content tableContent : tableContents) { + table.addContent(tableContent); + } + return table; + } + + /** + * Get the summary table caption. + * + * @param methodTypes set comprising of method types to show as table caption + * @return the caption for the summary table + */ + public Content getTableCaption(Set methodTypes) { + Content tabbedCaption = new HtmlTree(HtmlTag.CAPTION); + for (MethodTypes type : methodTypes) { + Content captionSpan; + Content span; + if (type.isDefaultTab()) { + captionSpan = HtmlTree.SPAN(configuration.getResource(type.resourceKey())); + span = HtmlTree.SPAN(type.tabId(), + HtmlStyle.activeTableTab, captionSpan); + } else { + captionSpan = HtmlTree.SPAN(getMethodTypeLinks(type)); + span = HtmlTree.SPAN(type.tabId(), + HtmlStyle.tableTab, captionSpan); + } + Content tabSpan = HtmlTree.SPAN(HtmlStyle.tabEnd, getSpace()); + span.addContent(tabSpan); + tabbedCaption.addContent(span); + } + return tabbedCaption; + } + + /** + * Get the method type links for the table caption. + * + * @param methodType the method type to be displayed as link + * @return the content tree for the method type link + */ + public Content getMethodTypeLinks(MethodTypes methodType) { + String jsShow = "javascript:show(" + methodType.value() +");"; + HtmlTree link = HtmlTree.A(jsShow, configuration.getResource(methodType.resourceKey())); + return link; + } + + /** + * Add the inherited summary header. + * + * @param mw the writer for the member being documented + * @param typeElement the te to be documented + * @param inheritedTree the content tree to which the inherited summary header will be added + */ + public void addInheritedSummaryHeader(AbstractMemberWriter mw, TypeElement typeElement, + Content inheritedTree) { + mw.addInheritedSummaryAnchor(typeElement, inheritedTree); + mw.addInheritedSummaryLabel(typeElement, inheritedTree); + } + + /** + * Add the index comment. + * + * @param member the member being documented + * @param contentTree the content tree to which the comment will be added + */ + protected void addIndexComment(Element member, Content contentTree) { + List tags = utils.getFirstSentenceTrees(member); + addIndexComment(member, tags, contentTree); + } + + /** + * Add the index comment. + * + * @param member the member being documented + * @param firstSentenceTags the first sentence tags for the member to be documented + * @param tdSummary the content tree to which the comment will be added + */ + protected void addIndexComment(Element member, List firstSentenceTags, + Content tdSummary) { + List deprs = utils.getBlockTags(member, DocTree.Kind.DEPRECATED); + Content div; + if (utils.isDeprecated(member)) { + Content deprLabel = HtmlTree.SPAN(HtmlStyle.deprecatedLabel, deprecatedPhrase); + div = HtmlTree.DIV(HtmlStyle.block, deprLabel); + div.addContent(getSpace()); + if (!deprs.isEmpty()) { + addInlineDeprecatedComment(member, deprs.get(0), div); + } + tdSummary.addContent(div); + return; + } else { + Element te = member.getEnclosingElement(); + if (te != null && utils.isTypeElement(te) && utils.isDeprecated(te)) { + Content deprLabel = HtmlTree.SPAN(HtmlStyle.deprecatedLabel, deprecatedPhrase); + div = HtmlTree.DIV(HtmlStyle.block, deprLabel); + div.addContent(getSpace()); + tdSummary.addContent(div); + } + } + addSummaryComment(member, firstSentenceTags, tdSummary); + } + + /** + * Add the summary type for the member. + * + * @param mw the writer for the member being documented + * @param member the member to be documented + * @param tdSummaryType the content tree to which the type will be added + */ + public void addSummaryType(AbstractMemberWriter mw, Element member, Content tdSummaryType) { + mw.addSummaryType(member, tdSummaryType); + } + + /** + * Add the summary link for the member. + * + * @param mw the writer for the member being documented + * @param member the member to be documented + * @param contentTree the content tree to which the link will be added + */ + public void addSummaryLinkComment(AbstractMemberWriter mw, Element member, Content contentTree) { + List tags = utils.getFirstSentenceTrees(member); + addSummaryLinkComment(mw, member, tags, contentTree); + } + + /** + * Add the summary link comment. + * + * @param mw the writer for the member being documented + * @param member the member being documented + * @param firstSentenceTags the first sentence tags for the member to be documented + * @param tdSummary the content tree to which the comment will be added + */ + public void addSummaryLinkComment(AbstractMemberWriter mw, + Element member, List firstSentenceTags, Content tdSummary) { + addIndexComment(member, firstSentenceTags, tdSummary); + } + + /** + * Add the inherited member summary. + * + * @param mw the writer for the member being documented + * @param typeElement the class being documented + * @param member the member being documented + * @param isFirst true if its the first link being documented + * @param linksTree the content tree to which the summary will be added + */ + public void addInheritedMemberSummary(AbstractMemberWriter mw, TypeElement typeElement, + Element member, boolean isFirst, Content linksTree) { + if (! isFirst) { + linksTree.addContent(", "); + } + mw.addInheritedSummaryLink(typeElement, member, linksTree); + } + + /** + * Get the document content header tree + * + * @return a content tree the document content header + */ + public Content getContentHeader() { + HtmlTree div = new HtmlTree(HtmlTag.DIV); + div.addStyle(HtmlStyle.contentContainer); + return div; + } + + /** + * Add the class content tree. + * + * @param contentTree content tree to which the class content will be added + * @param classContentTree class content tree which will be added to the content tree + */ + public void addClassContentTree(Content contentTree, Content classContentTree) { + if (configuration.allowTag(HtmlTag.MAIN)) { + mainTree.addContent(classContentTree); + contentTree.addContent(mainTree); + } else { + contentTree.addContent(classContentTree); + } + } + + /** + * Add the annotation content tree. + * + * @param contentTree content tree to which the annotation content will be added + * @param annotationContentTree annotation content tree which will be added to the content tree + */ + public void addAnnotationContentTree(Content contentTree, Content annotationContentTree) { + addClassContentTree(contentTree, annotationContentTree); + } + + /** + * Get the member header tree + * + * @return a content tree the member header + */ + public Content getMemberTreeHeader() { + HtmlTree li = new HtmlTree(HtmlTag.LI); + li.addStyle(HtmlStyle.blockList); + return li; + } + + /** + * Add the member tree. + * + * @param memberSummaryTree the content tree representing the member summary + * @param memberTree the content tree representing the member + */ + public void addMemberTree(Content memberSummaryTree, Content memberTree) { + if (configuration.allowTag(HtmlTag.SECTION)) { + HtmlTree htmlTree = HtmlTree.SECTION(getMemberTree(memberTree)); + memberSummaryTree.addContent(htmlTree); + } else { + memberSummaryTree.addContent(getMemberTree(memberTree)); + } + } + + /** + * Get the member tree + * + * @param contentTree the tree used to generate the complete member tree + * @return a content tree for the member + */ + public Content getMemberTree(Content contentTree) { + Content ul = HtmlTree.UL(HtmlStyle.blockList, contentTree); + return ul; + } + + /** + * Get the member summary tree + * + * @param contentTree the tree used to generate the member summary tree + * @return a content tree for the member summary + */ + public Content getMemberSummaryTree(Content contentTree) { + return getMemberTree(HtmlStyle.summary, contentTree); + } + + /** + * Get the member details tree + * + * @param contentTree the tree used to generate the member details tree + * @return a content tree for the member details + */ + public Content getMemberDetailsTree(Content contentTree) { + return getMemberTree(HtmlStyle.details, contentTree); + } + + /** + * Get the member tree + * + * @param style the style class to be added to the content tree + * @param contentTree the tree used to generate the complete member tree + */ + public Content getMemberTree(HtmlStyle style, Content contentTree) { + Content div = HtmlTree.DIV(style, getMemberTree(contentTree)); + return div; + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TagletWriterImpl.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TagletWriterImpl.java new file mode 100644 index 00000000000..1ae644233b2 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TagletWriterImpl.java @@ -0,0 +1,445 @@ +/* + * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.util.List; +import javax.lang.model.element.Element; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.SimpleElementVisitor9; + +import com.sun.source.doctree.DocTree; +import com.sun.source.doctree.IndexTree; +import com.sun.tools.javac.util.DefinedBy; + +import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.RawHtml; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.Configuration; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.builders.SerializedFormBuilder; +import jdk.javadoc.internal.doclets.toolkit.taglets.TagletWriter; +import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper; +import jdk.javadoc.internal.doclets.toolkit.util.DocLink; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; +import jdk.javadoc.internal.doclets.toolkit.util.DocletConstants; +import jdk.javadoc.internal.doclets.toolkit.util.MessageRetriever; +import jdk.javadoc.internal.doclets.toolkit.util.Utils; + +/** + * The taglet writer that writes HTML. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Jamie Ho + * @author Bhavesh Patel (Modified) + */ + +public class TagletWriterImpl extends TagletWriter { + + private final HtmlDocletWriter htmlWriter; + private final ConfigurationImpl configuration; + private final Utils utils; + + public TagletWriterImpl(HtmlDocletWriter htmlWriter, boolean isFirstSentence) { + super(isFirstSentence); + this.htmlWriter = htmlWriter; + configuration = htmlWriter.configuration; + this.utils = configuration.utils; + } + + /** + * {@inheritDoc} + */ + public Content getOutputInstance() { + return new ContentBuilder(); + } + + /** + * {@inheritDoc} + */ + protected Content codeTagOutput(Element element, DocTree tag) { + CommentHelper ch = utils.getCommentHelper(element); + String str = utils.normalizeNewlines(ch.getText(tag)); + StringContent content = new StringContent(str); + Content result = HtmlTree.CODE(content); + return result; + } + + protected Content indexTagOutput(Element element, DocTree tag) { + CommentHelper ch = utils.getCommentHelper(element); + IndexTree itt = (IndexTree)tag; + + String tagText = ch.getText(itt.getSearchTerm()); + if (tagText.charAt(0) == '"' && tagText.charAt(tagText.length() - 1) == '"') { + tagText = tagText.substring(1, tagText.length() - 1); + } + String desc = ch.getText(itt.getDescription()); + + String anchorName = htmlWriter.getName(tagText); + Content result = HtmlTree.A_ID(anchorName, new StringContent(tagText)); + if (configuration.createindex && !tagText.isEmpty()) { + SearchIndexItem si = new SearchIndexItem(); + si.setLabel(tagText); + si.setDescription(desc); + new SimpleElementVisitor9() { + @Override @DefinedBy(DefinedBy.Api.LANGUAGE_MODEL) + public Void visitPackage(PackageElement e, Void p) { + si.setUrl(DocPath.forPackage(e).getPath() + + "/" + DocPaths.PACKAGE_SUMMARY.getPath() + "#" + anchorName); + si.setHolder(utils.getSimpleName(element)); + return null; + } + + @Override @DefinedBy(DefinedBy.Api.LANGUAGE_MODEL) + public Void visitType(TypeElement e, Void p) { + si.setUrl(DocPath.forClass(utils, e).getPath() + "#" + anchorName); + si.setHolder(utils.getFullyQualifiedName(e)); + return null; + } + + @Override @DefinedBy(DefinedBy.Api.LANGUAGE_MODEL) + public Void visitVariable(VariableElement e, Void p) { + TypeElement te = utils.getEnclosingTypeElement(e); + si.setUrl(DocPath.forClass(utils, te).getPath() + "#" + anchorName); + si.setHolder(utils.getFullyQualifiedName(e) + "." + utils.getSimpleName(e)); + return null; + } + + @Override @DefinedBy(DefinedBy.Api.LANGUAGE_MODEL) + protected Void defaultAction(Element e, Void p) { + TypeElement te = utils.getEnclosingTypeElement(e); + si.setUrl(DocPath.forClass(utils, te).getPath() + "#" + anchorName); + si.setHolder(utils.getFullyQualifiedName(e)); + return null; + } + }.visit(element); + si.setCategory(configuration.getResource("doclet.SearchTags").toString()); + configuration.tagSearchIndex.add(si); + } + return result; + } + + /** + * {@inheritDoc} + */ + public Content getDocRootOutput() { + String path; + if (htmlWriter.pathToRoot.isEmpty()) + path = "."; + else + path = htmlWriter.pathToRoot.getPath(); + return new StringContent(path); + } + + /** + * {@inheritDoc} + */ + public Content deprecatedTagOutput(Element element) { + ContentBuilder result = new ContentBuilder(); + CommentHelper ch = utils.getCommentHelper(element); + List deprs = utils.getBlockTags(element, DocTree.Kind.DEPRECATED); + if (utils.isTypeElement(element)) { + if (utils.isDeprecated(element)) { + result.addContent(HtmlTree.SPAN(HtmlStyle.deprecatedLabel, + new StringContent(configuration.getText("doclet.Deprecated")))); + result.addContent(RawHtml.nbsp); + if (!deprs.isEmpty()) { + List commentTags = ch.getDescription(configuration, deprs.get(0)); + if (!commentTags.isEmpty()) { + result.addContent(commentTagsToOutput(null, element, commentTags, false)); + } + } + } + } else { + if (utils.isDeprecated(element)) { + result.addContent(HtmlTree.SPAN(HtmlStyle.deprecatedLabel, + new StringContent(configuration.getText("doclet.Deprecated")))); + result.addContent(RawHtml.nbsp); + if (!deprs.isEmpty()) { + List bodyTags = ch.getBody(configuration, deprs.get(0)); + Content body = commentTagsToOutput(null, element, bodyTags, false); + if (!body.isEmpty()) + result.addContent(HtmlTree.SPAN(HtmlStyle.deprecationComment, body)); + } + } else { + if (utils.isDeprecated(utils.getEnclosingTypeElement(element))) { + result.addContent(HtmlTree.SPAN(HtmlStyle.deprecatedLabel, + new StringContent(configuration.getText("doclet.Deprecated")))); + result.addContent(RawHtml.nbsp); + } + } + } + return result; + } + + /** + * {@inheritDoc} + */ + protected Content literalTagOutput(Element element, DocTree tag) { + CommentHelper ch = utils.getCommentHelper(element); + Content result = new StringContent(utils.normalizeNewlines(ch.getText(tag))); + return result; + } + + /** + * {@inheritDoc} + */ + public MessageRetriever getMsgRetriever() { + return configuration.message; + } + + /** + * {@inheritDoc} + */ + public Content getParamHeader(String header) { + HtmlTree result = HtmlTree.DT(HtmlTree.SPAN(HtmlStyle.paramLabel, + new StringContent(header))); + return result; + } + + /** + * {@inheritDoc} + */ + public Content paramTagOutput(Element element, DocTree paramTag, String paramName) { + ContentBuilder body = new ContentBuilder(); + CommentHelper ch = utils.getCommentHelper(element); + body.addContent(HtmlTree.CODE(new RawHtml(paramName))); + body.addContent(" - "); + List description = ch.getDescription(configuration, paramTag); + body.addContent(htmlWriter.commentTagsToContent(paramTag, element, description, false)); + HtmlTree result = HtmlTree.DD(body); + return result; + } + + /** + * {@inheritDoc} + */ + public Content propertyTagOutput(Element element, DocTree tag, String prefix) { + Content body = new ContentBuilder(); + CommentHelper ch = utils.getCommentHelper(element); + body.addContent(new RawHtml(prefix)); + body.addContent(" "); + body.addContent(HtmlTree.CODE(new RawHtml(ch.getText(tag)))); + body.addContent("."); + Content result = HtmlTree.P(body); + return result; + } + + /** + * {@inheritDoc} + */ + public Content returnTagOutput(Element element, DocTree returnTag) { + ContentBuilder result = new ContentBuilder(); + CommentHelper ch = utils.getCommentHelper(element); + result.addContent(HtmlTree.DT(HtmlTree.SPAN(HtmlStyle.returnLabel, + new StringContent(configuration.getText("doclet.Returns"))))); + result.addContent(HtmlTree.DD(htmlWriter.commentTagsToContent( + returnTag, element, ch.getDescription(configuration, returnTag), false))); + return result; + } + + /** + * {@inheritDoc} + */ + public Content seeTagOutput(Element holder, List seeTags) { + ContentBuilder body = new ContentBuilder(); + if (!seeTags.isEmpty()) { + for (DocTree dt : seeTags) { + appendSeparatorIfNotEmpty(body); + body.addContent(htmlWriter.seeTagToContent(holder, dt)); + } + } + if (utils.isVariableElement(holder) && ((VariableElement)holder).getConstantValue() != null && + htmlWriter instanceof ClassWriterImpl) { + //Automatically add link to constant values page for constant fields. + appendSeparatorIfNotEmpty(body); + DocPath constantsPath = + htmlWriter.pathToRoot.resolve(DocPaths.CONSTANT_VALUES); + String whichConstant = + ((ClassWriterImpl) htmlWriter).getTypeElement().getQualifiedName() + "." + + utils.getSimpleName(holder); + DocLink link = constantsPath.fragment(whichConstant); + body.addContent(htmlWriter.getHyperLink(link, + new StringContent(configuration.getText("doclet.Constants_Summary")))); + } + if (utils.isClass(holder) && utils.isSerializable((TypeElement)holder)) { + //Automatically add link to serialized form page for serializable classes. + if (SerializedFormBuilder.serialInclude(utils, holder) && + SerializedFormBuilder.serialInclude(utils, utils.containingPackage(holder))) { + appendSeparatorIfNotEmpty(body); + DocPath serialPath = htmlWriter.pathToRoot.resolve(DocPaths.SERIALIZED_FORM); + DocLink link = serialPath.fragment(utils.getFullyQualifiedName(holder)); + body.addContent(htmlWriter.getHyperLink(link, + new StringContent(configuration.getText("doclet.Serialized_Form")))); + } + } + if (body.isEmpty()) + return body; + + ContentBuilder result = new ContentBuilder(); + result.addContent(HtmlTree.DT(HtmlTree.SPAN(HtmlStyle.seeLabel, + new StringContent(configuration.getText("doclet.See_Also"))))); + result.addContent(HtmlTree.DD(body)); + return result; + + } + + private void appendSeparatorIfNotEmpty(ContentBuilder body) { + if (!body.isEmpty()) { + body.addContent(", "); + body.addContent(DocletConstants.NL); + } + } + + /** + * {@inheritDoc} + */ + public Content simpleTagOutput(Element element, List simpleTags, String header) { + CommentHelper ch = utils.getCommentHelper(element); + ContentBuilder result = new ContentBuilder(); + result.addContent(HtmlTree.DT(HtmlTree.SPAN(HtmlStyle.simpleTagLabel, new RawHtml(header)))); + ContentBuilder body = new ContentBuilder(); + boolean many = false; + for (DocTree simpleTag : simpleTags) { + if (many) { + body.addContent(", "); + } + List bodyTags = ch.getBody(configuration, simpleTag); + body.addContent(htmlWriter.commentTagsToContent(simpleTag, element, bodyTags, false)); + many = true; + } + result.addContent(HtmlTree.DD(body)); + return result; + } + + /** + * {@inheritDoc} + */ + public Content simpleTagOutput(Element element, DocTree simpleTag, String header) { + ContentBuilder result = new ContentBuilder(); + result.addContent(HtmlTree.DT(HtmlTree.SPAN(HtmlStyle.simpleTagLabel, new RawHtml(header)))); + CommentHelper ch = utils.getCommentHelper(element); + List description = ch.getDescription(configuration, simpleTag); + Content body = htmlWriter.commentTagsToContent(simpleTag, element, description, false); + result.addContent(HtmlTree.DD(body)); + return result; + } + + /** + * {@inheritDoc} + */ + public Content getThrowsHeader() { + HtmlTree result = HtmlTree.DT(HtmlTree.SPAN(HtmlStyle.throwsLabel, + new StringContent(configuration.getText("doclet.Throws")))); + return result; + } + + /** + * {@inheritDoc} + */ + public Content throwsTagOutput(Element element, DocTree throwsTag) { + ContentBuilder body = new ContentBuilder(); + CommentHelper ch = utils.getCommentHelper(element); + Element exception = ch.getException(configuration, throwsTag); + Content excName; + if (exception == null) { + excName = new RawHtml(ch.getExceptionName(throwsTag).toString()); + } else if (exception.asType() == null) { + excName = new RawHtml(utils.getFullyQualifiedName(exception)); + } else { + LinkInfoImpl link = new LinkInfoImpl(configuration, LinkInfoImpl.Kind.MEMBER, + exception.asType()); + link.excludeTypeBounds = true; + excName = htmlWriter.getLink(link); + } + body.addContent(HtmlTree.CODE(excName)); + List description = ch.getDescription(configuration, throwsTag); + Content desc = htmlWriter.commentTagsToContent(throwsTag, element, description, false); + if (desc != null && !desc.isEmpty()) { + body.addContent(" - "); + body.addContent(desc); + } + HtmlTree result = HtmlTree.DD(body); + return result; + } + + /** + * {@inheritDoc} + */ + public Content throwsTagOutput(TypeMirror throwsType) { + HtmlTree result = HtmlTree.DD(HtmlTree.CODE(htmlWriter.getLink( + new LinkInfoImpl(configuration, LinkInfoImpl.Kind.MEMBER, throwsType)))); + return result; + } + + /** + * {@inheritDoc} + */ + public Content valueTagOutput(VariableElement field, String constantVal, boolean includeLink) { + return includeLink ? + htmlWriter.getDocLink(LinkInfoImpl.Kind.VALUE_TAG, field, + constantVal, false) : new RawHtml(constantVal); + } + + /** + * {@inheritDoc} + */ + public Content commentTagsToOutput(DocTree holderTag, List tags) { + return commentTagsToOutput(holderTag, null, tags, false); + } + + /** + * {@inheritDoc} + */ + public Content commentTagsToOutput(Element holder, List tags) { + return commentTagsToOutput(null, holder, tags, false); + } + + /** + * {@inheritDoc} + */ + public Content commentTagsToOutput(DocTree holderTag, + Element holder, List tags, boolean isFirstSentence) { + return htmlWriter.commentTagsToContent(holderTag, holder, + tags, isFirstSentence); + } + + /** + * {@inheritDoc} + */ + public Configuration configuration() { + return configuration; + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TreeWriter.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TreeWriter.java new file mode 100644 index 00000000000..0987cc2d825 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TreeWriter.java @@ -0,0 +1,206 @@ +/* + * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.IOException; +import java.util.SortedSet; + +import javax.lang.model.element.PackageElement; + +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; +import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.ClassTree; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; +import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; + +/** + * Generate Class Hierarchy page for all the Classes in this run. Use + * ClassTree for building the Tree. The name of + * the generated file is "overview-tree.html" and it is generated in the + * current or the destination directory. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Atul M Dambalkar + * @author Bhavesh Patel (Modified) + */ +public class TreeWriter extends AbstractTreeWriter { + + /** + * Packages in this run. + */ + SortedSet packages; + + /** + * True if there are no packages specified on the command line, + * False otherwise. + */ + private boolean classesonly; + + /** + * Constructor to construct TreeWriter object. + * + * @param configuration the current configuration of the doclet. + * @param filename String filename + * @param classtree the tree being built. + */ + public TreeWriter(ConfigurationImpl configuration, + DocPath filename, ClassTree classtree) throws IOException { + super(configuration, filename, classtree); + packages = configuration.packages; + classesonly = packages.isEmpty(); + } + + /** + * Create a TreeWriter object and use it to generate the + * "overview-tree.html" file. + * + * @param classtree the class tree being documented. + * @throws DocletAbortException + */ + public static void generate(ConfigurationImpl configuration, + ClassTree classtree) { + TreeWriter treegen; + DocPath filename = DocPaths.OVERVIEW_TREE; + try { + treegen = new TreeWriter(configuration, filename, classtree); + treegen.generateTreeFile(); + treegen.close(); + } catch (IOException exc) { + configuration.standardmessage.error( + "doclet.exception_encountered", + exc.toString(), filename); + throw new DocletAbortException(exc); + } + } + + /** + * Generate the interface hierarchy and class hierarchy. + */ + public void generateTreeFile() throws IOException { + HtmlTree body = getTreeHeader(); + Content headContent = getResource("doclet.Hierarchy_For_All_Packages"); + Content heading = HtmlTree.HEADING(HtmlConstants.TITLE_HEADING, false, + HtmlStyle.title, headContent); + Content div = HtmlTree.DIV(HtmlStyle.header, heading); + addPackageTreeLinks(div); + HtmlTree htmlTree = (configuration.allowTag(HtmlTag.MAIN)) + ? HtmlTree.MAIN() + : body; + htmlTree.addContent(div); + HtmlTree divTree = new HtmlTree(HtmlTag.DIV); + divTree.addStyle(HtmlStyle.contentContainer); + addTree(classtree.baseClasses(), "doclet.Class_Hierarchy", divTree); + addTree(classtree.baseInterfaces(), "doclet.Interface_Hierarchy", divTree); + addTree(classtree.baseAnnotationTypes(), "doclet.Annotation_Type_Hierarchy", divTree); + addTree(classtree.baseEnums(), "doclet.Enum_Hierarchy", divTree, true); + htmlTree.addContent(divTree); + if (configuration.allowTag(HtmlTag.MAIN)) { + body.addContent(htmlTree); + } + if (configuration.allowTag(HtmlTag.FOOTER)) { + htmlTree = HtmlTree.FOOTER(); + } else { + htmlTree = body; + } + addNavLinks(false, htmlTree); + addBottom(htmlTree); + if (configuration.allowTag(HtmlTag.FOOTER)) { + body.addContent(htmlTree); + } + printHtmlDocument(null, true, body); + } + + /** + * Add the links to all the package tree files. + * + * @param contentTree the content tree to which the links will be added + */ + protected void addPackageTreeLinks(Content contentTree) { + //Do nothing if only unnamed package is used + if (isUnnamedPackage()) { + return; + } + if (!classesonly) { + Content span = HtmlTree.SPAN(HtmlStyle.packageHierarchyLabel, + getResource("doclet.Package_Hierarchies")); + contentTree.addContent(span); + HtmlTree ul = new HtmlTree(HtmlTag.UL); + ul.addStyle(HtmlStyle.horizontal); + int i = 0; + for (PackageElement pkg : packages) { + // If the package name length is 0 or if -nodeprecated option + // is set and the package is marked as deprecated, do not include + // the page in the list of package hierarchies. + if (pkg.isUnnamed() || + (configuration.nodeprecated && utils.isDeprecated(pkg))) { + i++; + continue; + } + DocPath link = pathString(pkg, DocPaths.PACKAGE_TREE); + Content li = HtmlTree.LI(getHyperLink(link, + new StringContent(utils.getPackageName(pkg)))); + if (i < packages.size() - 1) { + li.addContent(", "); + } + ul.addContent(li); + i++; + } + contentTree.addContent(ul); + } + } + + /** + * Get the tree header. + * + * @return a content tree for the tree header + */ + protected HtmlTree getTreeHeader() { + String title = configuration.getText("doclet.Window_Class_Hierarchy"); + HtmlTree bodyTree = getBody(true, getWindowTitle(title)); + HtmlTree htmlTree = (configuration.allowTag(HtmlTag.HEADER)) + ? HtmlTree.HEADER() + : bodyTree; + addTop(htmlTree); + addNavLinks(true, htmlTree); + if (configuration.allowTag(HtmlTag.HEADER)) { + bodyTree.addContent(htmlTree); + } + return bodyTree; + } + + private boolean isUnnamedPackage() { + return packages.size() == 1 && packages.first().isUnnamed(); + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/WriterFactoryImpl.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/WriterFactoryImpl.java new file mode 100644 index 00000000000..cce4052b9b3 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/WriterFactoryImpl.java @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html; + +import java.io.IOException; + +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + +import jdk.javadoc.internal.doclets.toolkit.AnnotationTypeFieldWriter; +import jdk.javadoc.internal.doclets.toolkit.AnnotationTypeOptionalMemberWriter; +import jdk.javadoc.internal.doclets.toolkit.AnnotationTypeRequiredMemberWriter; +import jdk.javadoc.internal.doclets.toolkit.AnnotationTypeWriter; +import jdk.javadoc.internal.doclets.toolkit.ClassWriter; +import jdk.javadoc.internal.doclets.toolkit.ConstantsSummaryWriter; +import jdk.javadoc.internal.doclets.toolkit.MemberSummaryWriter; +import jdk.javadoc.internal.doclets.toolkit.PackageSummaryWriter; +import jdk.javadoc.internal.doclets.toolkit.SerializedFormWriter; +import jdk.javadoc.internal.doclets.toolkit.WriterFactory; +import jdk.javadoc.internal.doclets.toolkit.util.ClassTree; +import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberMap; + +import static jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberMap.Kind.*; + +/** + * The factory that returns HTML writers. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Jamie Ho + */ +public class WriterFactoryImpl implements WriterFactory { + + private final ConfigurationImpl configuration; + public WriterFactoryImpl(ConfigurationImpl configuration) { + this.configuration = configuration; + } + + /** + * {@inheritDoc} + */ + @Override + public ConstantsSummaryWriter getConstantsSummaryWriter() throws Exception { + return new ConstantsSummaryWriterImpl(configuration); + } + + /** + * {@inheritDoc} + */ + @Override + public PackageSummaryWriter getPackageSummaryWriter(PackageElement packageElement, + PackageElement prevPkg, PackageElement nextPkg) throws Exception { + return new PackageWriterImpl(configuration, packageElement, prevPkg, nextPkg); + } + + /** + * {@inheritDoc} + */ + @Override + public ClassWriter getClassWriter(TypeElement typeElement, TypeElement prevClass, + TypeElement nextClass, ClassTree classTree) throws IOException { + return new ClassWriterImpl(configuration, typeElement, prevClass, nextClass, classTree); + } + + /** + * {@inheritDoc} + */ + @Override + public AnnotationTypeWriter getAnnotationTypeWriter(TypeElement annotationType, + TypeMirror prevType, TypeMirror nextType) throws Exception { + return new AnnotationTypeWriterImpl(configuration, annotationType, prevType, nextType); + } + + /** + * {@inheritDoc} + */ + @Override + public AnnotationTypeFieldWriter + getAnnotationTypeFieldWriter(AnnotationTypeWriter annotationTypeWriter) throws Exception { + TypeElement te = annotationTypeWriter.getAnnotationTypeElement(); + return new AnnotationTypeFieldWriterImpl( + (SubWriterHolderWriter) annotationTypeWriter, te); + } + + /** + * {@inheritDoc} + */ + @Override + public AnnotationTypeOptionalMemberWriter + getAnnotationTypeOptionalMemberWriter( + AnnotationTypeWriter annotationTypeWriter) throws Exception { + TypeElement te = annotationTypeWriter.getAnnotationTypeElement(); + return new AnnotationTypeOptionalMemberWriterImpl( + (SubWriterHolderWriter) annotationTypeWriter, te); + } + + /** + * {@inheritDoc} + */ + @Override + public AnnotationTypeRequiredMemberWriter + getAnnotationTypeRequiredMemberWriter(AnnotationTypeWriter annotationTypeWriter) throws Exception { + TypeElement te = annotationTypeWriter.getAnnotationTypeElement(); + return new AnnotationTypeRequiredMemberWriterImpl( + (SubWriterHolderWriter) annotationTypeWriter, te); + } + + /** + * {@inheritDoc} + */ + @Override + public EnumConstantWriterImpl getEnumConstantWriter(ClassWriter classWriter) + throws Exception { + return new EnumConstantWriterImpl((SubWriterHolderWriter) classWriter, + classWriter.getTypeElement()); + } + + /** + * {@inheritDoc} + */ + @Override + public FieldWriterImpl getFieldWriter(ClassWriter classWriter) + throws Exception { + return new FieldWriterImpl((SubWriterHolderWriter) classWriter, classWriter.getTypeElement()); + } + + /** + * {@inheritDoc} + */ + @Override + public PropertyWriterImpl getPropertyWriter(ClassWriter classWriter) + throws Exception { + return new PropertyWriterImpl((SubWriterHolderWriter) classWriter, + classWriter.getTypeElement()); + } + + /** + * {@inheritDoc} + */ + @Override + public MethodWriterImpl getMethodWriter(ClassWriter classWriter) + throws Exception { + return new MethodWriterImpl((SubWriterHolderWriter) classWriter, classWriter.getTypeElement()); + } + + /** + * {@inheritDoc} + */ + @Override + public ConstructorWriterImpl getConstructorWriter(ClassWriter classWriter) + throws Exception { + return new ConstructorWriterImpl((SubWriterHolderWriter) classWriter, + classWriter.getTypeElement()); + } + + /** + * {@inheritDoc} + */ + @Override + public MemberSummaryWriter getMemberSummaryWriter( + ClassWriter classWriter, VisibleMemberMap.Kind memberType) + throws Exception { + switch (memberType) { + case CONSTRUCTORS: + return getConstructorWriter(classWriter); + case ENUM_CONSTANTS: + return getEnumConstantWriter(classWriter); + case FIELDS: + return getFieldWriter(classWriter); + case PROPERTIES: + return getPropertyWriter(classWriter); + case INNER_CLASSES: + return new NestedClassWriterImpl((SubWriterHolderWriter) + classWriter, classWriter.getTypeElement()); + case METHODS: + return getMethodWriter(classWriter); + default: + return null; + } + } + + /** + * {@inheritDoc} + */ + @Override + public MemberSummaryWriter getMemberSummaryWriter( + AnnotationTypeWriter annotationTypeWriter, VisibleMemberMap.Kind memberType) + throws Exception { + switch (memberType) { + case ANNOTATION_TYPE_FIELDS: + return (AnnotationTypeFieldWriterImpl) + getAnnotationTypeFieldWriter(annotationTypeWriter); + case ANNOTATION_TYPE_MEMBER_OPTIONAL: + return (AnnotationTypeOptionalMemberWriterImpl) + getAnnotationTypeOptionalMemberWriter(annotationTypeWriter); + case ANNOTATION_TYPE_MEMBER_REQUIRED: + return (AnnotationTypeRequiredMemberWriterImpl) + getAnnotationTypeRequiredMemberWriter(annotationTypeWriter); + default: + return null; + } + } + + /** + * {@inheritDoc} + */ + @Override + public SerializedFormWriter getSerializedFormWriter() throws Exception { + return new SerializedFormWriterImpl(configuration); + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/Comment.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/Comment.java new file mode 100644 index 00000000000..0adfc088106 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/Comment.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html.markup; + +import java.io.IOException; +import java.io.Writer; + +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; +import jdk.javadoc.internal.doclets.toolkit.util.DocletConstants; + +/** + * Class for generating a comment for HTML pages of javadoc output. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Bhavesh Patel + */ +public class Comment extends Content { + + private String commentText; + + /** + * Constructor to construct a Comment object. + * + * @param comment comment text for the comment + */ + public Comment(String comment) { + commentText = nullCheck(comment); + } + + /** + * This method is not supported by the class. + * + * @param content content that needs to be added + * @throws DocletAbortException this method will always throw a + * DocletAbortException because it + * is not supported. + */ + public void addContent(Content content) { + throw new DocletAbortException("not supported"); + } + + /** + * This method is not supported by the class. + * + * @param stringContent string content that needs to be added + * @throws DocletAbortException this method will always throw a + * DocletAbortException because it + * is not supported. + */ + public void addContent(String stringContent) { + throw new DocletAbortException("not supported"); + } + + /** + * {@inheritDoc} + */ + public boolean isEmpty() { + return commentText.isEmpty(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean write(Writer out, boolean atNewline) throws IOException { + if (!atNewline) + out.write(DocletConstants.NL); + out.write("" + DocletConstants.NL); + return true; + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/ContentBuilder.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/ContentBuilder.java new file mode 100644 index 00000000000..64ceef82473 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/ContentBuilder.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html.markup; + +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import jdk.javadoc.internal.doclets.toolkit.Content; + +/** + * A sequence of Content nodes. + */ +public class ContentBuilder extends Content { + protected List contents = Collections.emptyList(); + + @Override + public void addContent(Content content) { + nullCheck(content); + ensureMutableContents(); + if (content instanceof ContentBuilder) { + contents.addAll(((ContentBuilder) content).contents); + } else + contents.add(content); + } + + @Override + public void addContent(String text) { + if (text.isEmpty()) + return; + ensureMutableContents(); + Content c = contents.isEmpty() ? null : contents.get(contents.size() - 1); + StringContent sc; + if (c != null && c instanceof StringContent) { + sc = (StringContent) c; + } else { + contents.add(sc = new StringContent()); + } + sc.addContent(text); + } + + @Override + public boolean write(Writer writer, boolean atNewline) throws IOException { + for (Content content: contents) { + atNewline = content.write(writer, atNewline); + } + return atNewline; + } + + @Override + public boolean isEmpty() { + for (Content content: contents) { + if (!content.isEmpty()) + return false; + } + return true; + } + + @Override + public int charCount() { + int n = 0; + for (Content c : contents) + n += c.charCount(); + return n; + } + + private void ensureMutableContents() { + if (contents.isEmpty()) + contents = new ArrayList<>(); + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/DocType.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/DocType.java new file mode 100644 index 00000000000..469ee40743f --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/DocType.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html.markup; + +import java.io.IOException; +import java.io.Writer; + +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; +import jdk.javadoc.internal.doclets.toolkit.util.DocletConstants; + +/** + * Class for generating document type for HTML pages of javadoc output. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Bhavesh Patel + */ +public class DocType extends Content { + + private String docType; + + public static final DocType TRANSITIONAL = + new DocType("Transitional", "http://www.w3.org/TR/html4/loose.dtd"); + + public static final DocType HTML5 = new DocType(); + + /** + * Constructor to construct a DocType object. + * + * @param type the doctype to be added + * @param dtd the dtd of the doctype + */ + private DocType(String type, String dtd) { + docType = "" + DocletConstants.NL; + } + + /** + * Constructor to construct a DocType object. + */ + private DocType() { + docType = "" + DocletConstants.NL; + } + + /** + * This method is not supported by the class. + * + * @param content content that needs to be added + * @throws DocletAbortException this method will always throw a + * DocletAbortException because it + * is not supported. + */ + public void addContent(Content content) { + throw new DocletAbortException("not supported"); + } + + /** + * This method is not supported by the class. + * + * @param stringContent string content that needs to be added + * @throws DocletAbortException this method will always throw a + * DocletAbortException because it + * is not supported. + */ + public void addContent(String stringContent) { + throw new DocletAbortException("not supported"); + } + + /** + * {@inheritDoc} + */ + public boolean isEmpty() { + return (docType.length() == 0); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean write(Writer out, boolean atNewline) throws IOException { + out.write(docType); + return true; // guaranteed by constructor + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlAttr.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlAttr.java new file mode 100644 index 00000000000..69ae2aa5c0e --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlAttr.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html.markup; + +import jdk.javadoc.internal.doclets.toolkit.util.Utils; + +/** + * Enum representing HTML tag attributes. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Bhavesh Patel + */ +public enum HtmlAttr { + ALT, + CLASS, + CLEAR, + COLS, + CONTENT, + DISABLED, + HREF, + HTTP_EQUIV("http-equiv"), + ID, + LANG, + NAME, + ONLOAD, + REL, + ROLE, + ROWS, + SCOPE, + SCROLLING, + SRC, + SUMMARY, + TARGET, + TITLE, + TYPE, + VALUE, + WIDTH; + + private final String value; + + public enum Role { + + BANNER, + CONTENTINFO, + MAIN, + NAVIGATION, + REGION; + + private final String role; + + Role() { + role = Utils.toLowerCase(name()); + } + + public String toString() { + return role; + } + } + + HtmlAttr() { + this.value = Utils.toLowerCase(name()); + } + + HtmlAttr(String name) { + this.value = name; + } + + public String toString() { + return value; + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlConstants.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlConstants.java new file mode 100644 index 00000000000..b1e03e3e49b --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlConstants.java @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html.markup; + +import jdk.javadoc.internal.doclets.toolkit.Content; + +/** + * Stores constants for Html Doclet. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Bhavesh Patel + */ +public class HtmlConstants { + + /** + * Marker to identify start of top navigation bar. + */ + public static final Content START_OF_TOP_NAVBAR = + new Comment("========= START OF TOP NAVBAR ======="); + + /** + * Marker to identify start of bottom navigation bar. + */ + public static final Content START_OF_BOTTOM_NAVBAR = + new Comment("======= START OF BOTTOM NAVBAR ======"); + + /** + * Marker to identify end of top navigation bar. + */ + public static final Content END_OF_TOP_NAVBAR = + new Comment("========= END OF TOP NAVBAR ========="); + + /** + * Marker to identify end of bottom navigation bar. + */ + public static final Content END_OF_BOTTOM_NAVBAR = + new Comment("======== END OF BOTTOM NAVBAR ======="); + + /** + * Marker to identify start of class data. + */ + public static final Content START_OF_CLASS_DATA = + new Comment("======== START OF CLASS DATA ========"); + + /** + * Marker to identify end of class data. + */ + public static final Content END_OF_CLASS_DATA = + new Comment("========= END OF CLASS DATA ========="); + + /** + * Marker to identify start of nested class summary. + */ + public static final Content START_OF_NESTED_CLASS_SUMMARY = + new Comment("======== NESTED CLASS SUMMARY ========"); + + /** + * Marker to identify start of annotation type optional member summary. + */ + public static final Content START_OF_ANNOTATION_TYPE_OPTIONAL_MEMBER_SUMMARY = + new Comment("=========== ANNOTATION TYPE OPTIONAL MEMBER SUMMARY ==========="); + + /** + * Marker to identify start of annotation type required member summary. + */ + public static final Content START_OF_ANNOTATION_TYPE_REQUIRED_MEMBER_SUMMARY = + new Comment("=========== ANNOTATION TYPE REQUIRED MEMBER SUMMARY ==========="); + + /** + * Marker to identify start of annotation type required member summary. + */ + public static final Content START_OF_ANNOTATION_TYPE_FIELD_SUMMARY = + new Comment("=========== ANNOTATION TYPE FIELD SUMMARY ==========="); + + /** + * Marker to identify start of constructor summary. + */ + public static final Content START_OF_CONSTRUCTOR_SUMMARY = + new Comment("======== CONSTRUCTOR SUMMARY ========"); + + /** + * Marker to identify start of enum constants summary. + */ + public static final Content START_OF_ENUM_CONSTANT_SUMMARY = + new Comment("=========== ENUM CONSTANT SUMMARY ==========="); + + /** + * Marker to identify start of field summary. + */ + public static final Content START_OF_FIELD_SUMMARY = + new Comment("=========== FIELD SUMMARY ==========="); + + /** + * Marker to identify start of properties summary. + */ + public static final Content START_OF_PROPERTY_SUMMARY = + new Comment("=========== PROPERTY SUMMARY ==========="); + + /** + * Marker to identify start of method summary. + */ + public static final Content START_OF_METHOD_SUMMARY = + new Comment("========== METHOD SUMMARY ==========="); + + /** + * Marker to identify start of annotation type details. + */ + public static final Content START_OF_ANNOTATION_TYPE_DETAILS = + new Comment("============ ANNOTATION TYPE MEMBER DETAIL ==========="); + + /** + * Marker to identify start of annotation type field details. + */ + public static final Content START_OF_ANNOTATION_TYPE_FIELD_DETAILS = + new Comment("============ ANNOTATION TYPE FIELD DETAIL ==========="); + + /** + * Marker to identify start of method details. + */ + public static final Content START_OF_METHOD_DETAILS = + new Comment("============ METHOD DETAIL =========="); + + /** + * Marker to identify start of field details. + */ + public static final Content START_OF_FIELD_DETAILS = + new Comment("============ FIELD DETAIL ==========="); + + /** + * Marker to identify start of property details. + */ + public static final Content START_OF_PROPERTY_DETAILS = + new Comment("============ PROPERTY DETAIL ==========="); + + /** + * Marker to identify start of constructor details. + */ + public static final Content START_OF_CONSTRUCTOR_DETAILS = + new Comment("========= CONSTRUCTOR DETAIL ========"); + + /** + * Marker to identify start of enum constants details. + */ + public static final Content START_OF_ENUM_CONSTANT_DETAILS = + new Comment("============ ENUM CONSTANT DETAIL ==========="); + + /** + * Html tag for the page title heading. + */ + public static final HtmlTag TITLE_HEADING = HtmlTag.H1; + + /** + * Html tag for the class page title heading. + */ + public static final HtmlTag CLASS_PAGE_HEADING = HtmlTag.H2; + + /** + * Html tag for the content heading. + */ + public static final HtmlTag CONTENT_HEADING = HtmlTag.H2; + + /** + * Html tag for the package name heading. + */ + public static final HtmlTag PACKAGE_HEADING = HtmlTag.H2; + + /** + * Html tag for the member summary heading. + */ + public static final HtmlTag SUMMARY_HEADING = HtmlTag.H3; + + /** + * Html tag for the inherited member summary heading. + */ + public static final HtmlTag INHERITED_SUMMARY_HEADING = HtmlTag.H3; + + /** + * Html tag for the member details heading. + */ + public static final HtmlTag DETAILS_HEADING = HtmlTag.H3; + + /** + * Html tag for the serialized member heading. + */ + public static final HtmlTag SERIALIZED_MEMBER_HEADING = HtmlTag.H3; + + /** + * Html tag for the member heading. + */ + public static final HtmlTag MEMBER_HEADING = HtmlTag.H4; + + /** + * Default charset for HTML. + */ + public static final String HTML_DEFAULT_CHARSET = "utf-8"; +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlDocWriter.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlDocWriter.java new file mode 100644 index 00000000000..d7aec841525 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlDocWriter.java @@ -0,0 +1,361 @@ +/* + * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html.markup; + +import java.io.*; +import java.util.*; + +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; + +import jdk.javadoc.internal.doclets.formats.html.ConfigurationImpl; +import jdk.javadoc.internal.doclets.formats.html.SectionName; +import jdk.javadoc.internal.doclets.toolkit.Configuration; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.DocFile; +import jdk.javadoc.internal.doclets.toolkit.util.DocLink; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; + + +/** + * Class for the Html Format Code Generation specific to JavaDoc. + * This Class contains methods related to the Html Code Generation which + * are used by the Sub-Classes in the package jdk.javadoc.internal.tool.standard. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Atul M Dambalkar + * @author Robert Field + */ +public abstract class HtmlDocWriter extends HtmlWriter { + + public static final String CONTENT_TYPE = "text/html"; + + DocPath pathToRoot; + + /** + * Constructor. Initializes the destination file name through the super + * class HtmlWriter. + * + * @param filename String file name. + */ + public HtmlDocWriter(Configuration configuration, DocPath filename) + throws IOException { + super(configuration, filename); + this.pathToRoot = filename.parent().invert(); + configuration.message.notice("doclet.Generating_0", + DocFile.createFileForOutput(configuration, filename).getPath()); + } + + /** + * Accessor for configuration. + */ + public abstract Configuration configuration(); + + public Content getHyperLink(DocPath link, String label) { + return getHyperLink(link, new StringContent(label), false, "", "", ""); + } + + /** + * Get Html Hyper Link Content. + * + * @param where Position of the link in the file. Character '#' is not + * needed. + * @param label Tag for the link. + * @return a content tree for the hyper link + */ + public Content getHyperLink(String where, + Content label) { + return getHyperLink(getDocLink(where), label, "", ""); + } + + /** + * Get Html Hyper Link Content. + * + * @param sectionName The section name to which the link will be created. + * @param label Tag for the link. + * @return a content tree for the hyper link + */ + public Content getHyperLink(SectionName sectionName, + Content label) { + return getHyperLink(getDocLink(sectionName), label, "", ""); + } + + /** + * Get Html Hyper Link Content. + * + * @param sectionName The section name combined with where to which the link + * will be created. + * @param where The fragment combined with sectionName to which the link + * will be created. + * @param label Tag for the link. + * @return a content tree for the hyper link + */ + public Content getHyperLink(SectionName sectionName, String where, + Content label) { + return getHyperLink(getDocLink(sectionName, where), label, "", ""); + } + + /** + * Get the link. + * + * @param where Position of the link in the file. + * @return a DocLink object for the hyper link + */ + public DocLink getDocLink(String where) { + return DocLink.fragment(getName(where)); + } + + /** + * Get the link. + * + * @param sectionName The section name to which the link will be created. + * @return a DocLink object for the hyper link + */ + public DocLink getDocLink(SectionName sectionName) { + return DocLink.fragment(sectionName.getName()); + } + + /** + * Get the link. + * + * @param sectionName The section name combined with where to which the link + * will be created. + * @param where The fragment combined with sectionName to which the link + * will be created. + * @return a DocLink object for the hyper link + */ + public DocLink getDocLink(SectionName sectionName, String where) { + return DocLink.fragment(sectionName.getName() + getName(where)); + } + + /** + * Convert the name to a valid HTML name. + * + * @param name the name that needs to be converted to valid HTML name. + * @return a valid HTML name string. + */ + public String getName(String name) { + StringBuilder sb = new StringBuilder(); + char ch; + /* The HTML 4 spec at http://www.w3.org/TR/html4/types.html#h-6.2 mentions + * that the name/id should begin with a letter followed by other valid characters. + * The HTML 5 spec (draft) is more permissive on names/ids where the only restriction + * is that it should be at least one character long and should not contain spaces. + * The spec draft is @ http://www.w3.org/html/wg/drafts/html/master/dom.html#the-id-attribute. + * + * For HTML 4, we need to check for non-characters at the beginning of the name and + * substitute it accordingly, "_" and "$" can appear at the beginning of a member name. + * The method substitutes "$" with "Z:Z:D" and will prefix "_" with "Z:Z". + */ + for (int i = 0; i < name.length(); i++) { + ch = name.charAt(i); + switch (ch) { + case '(': + case ')': + case '<': + case '>': + case ',': + sb.append('-'); + break; + case ' ': + case '[': + break; + case ']': + sb.append(":A"); + break; + // Any appearance of $ needs to be substituted with ":D" and not with hyphen + // since a field name "P$$ and a method P(), both valid member names, can end + // up as "P--". A member name beginning with $ needs to be substituted with + // "Z:Z:D". + case '$': + if (i == 0) + sb.append("Z:Z"); + sb.append(":D"); + break; + // A member name beginning with _ needs to be prefixed with "Z:Z" since valid anchor + // names can only begin with a letter. + case '_': + if (i == 0) + sb.append("Z:Z"); + sb.append(ch); + break; + default: + sb.append(ch); + } + } + return sb.toString(); + } + + /** + * Get Html hyperlink. + * + * @param link path of the file. + * @param label Tag for the link. + * @return a content tree for the hyper link + */ + public Content getHyperLink(DocPath link, Content label) { + return getHyperLink(link, label, "", ""); + } + + public Content getHyperLink(DocLink link, Content label) { + return getHyperLink(link, label, "", ""); + } + + public Content getHyperLink(DocPath link, + Content label, boolean strong, + String stylename, String title, String target) { + return getHyperLink(new DocLink(link), label, strong, + stylename, title, target); + } + + public Content getHyperLink(DocLink link, + Content label, boolean strong, + String stylename, String title, String target) { + Content body = label; + if (strong) { + body = HtmlTree.SPAN(HtmlStyle.typeNameLink, body); + } + if (stylename != null && stylename.length() != 0) { + HtmlTree t = new HtmlTree(HtmlTag.FONT, body); + t.addAttr(HtmlAttr.CLASS, stylename); + body = t; + } + HtmlTree l = HtmlTree.A(link.toString(), body); + if (title != null && title.length() != 0) { + l.addAttr(HtmlAttr.TITLE, title); + } + if (target != null && target.length() != 0) { + l.addAttr(HtmlAttr.TARGET, target); + } + return l; + } + + /** + * Get Html Hyper Link. + * + * @param link String name of the file. + * @param label Tag for the link. + * @param title String that describes the link's content for accessibility. + * @param target Target frame. + * @return a content tree for the hyper link. + */ + public Content getHyperLink(DocPath link, Content label, String title, String target) { + return getHyperLink(new DocLink(link), label, title, target); + } + + public Content getHyperLink(DocLink link, Content label, String title, String target) { + HtmlTree anchor = HtmlTree.A(link.toString(), label); + if (title != null && title.length() != 0) { + anchor.addAttr(HtmlAttr.TITLE, title); + } + if (target != null && target.length() != 0) { + anchor.addAttr(HtmlAttr.TARGET, target); + } + return anchor; + } + + /** + * Get the enclosed name of the package + * + * @param te TypeElement + * @return the name + */ + public String getEnclosingPackageName(TypeElement te) { + + PackageElement encl = configuration.utils.containingPackage(te); + return (encl.isUnnamed()) ? "" : (encl.getQualifiedName() + "."); + } + + public boolean getMemberDetailsListPrinted() { + return memberDetailsListPrinted; + } + + /** + * Print the frames version of the Html file header. + * Called only when generating an HTML frames file. + * + * @param title Title of this HTML document + * @param configuration the configuration object + * @param body the body content tree to be added to the HTML document + */ + public void printFramesDocument(String title, ConfigurationImpl configuration, + HtmlTree body) throws IOException { + Content htmlDocType = configuration.isOutputHtml5() + ? DocType.HTML5 + : DocType.TRANSITIONAL; + Content htmlComment = new Comment(configuration.getText("doclet.New_Page")); + Content head = new HtmlTree(HtmlTag.HEAD); + head.addContent(getGeneratedBy(!configuration.notimestamp)); + Content windowTitle = HtmlTree.TITLE(new StringContent(title)); + head.addContent(windowTitle); + Content meta = HtmlTree.META("Content-Type", CONTENT_TYPE, + (configuration.charset.length() > 0) ? + configuration.charset : HtmlConstants.HTML_DEFAULT_CHARSET); + head.addContent(meta); + head.addContent(getStyleSheetProperties(configuration)); + head.addContent(getFramesJavaScript()); + Content htmlTree = HtmlTree.HTML(configuration.getLocale().getLanguage(), + head, body); + Content htmlDocument = new HtmlDocument(htmlDocType, + htmlComment, htmlTree); + write(htmlDocument); + } + + /** + * Returns a link to the stylesheet file. + * + * @return an HtmlTree for the lINK tag which provides the stylesheet location + */ + public HtmlTree getStyleSheetProperties(ConfigurationImpl configuration) { + String stylesheetfile = configuration.stylesheetfile; + DocPath stylesheet; + if (stylesheetfile.isEmpty()) { + stylesheet = DocPaths.STYLESHEET; + } else { + DocFile file = DocFile.createFileForInput(configuration, stylesheetfile); + stylesheet = DocPath.create(file.getName()); + } + HtmlTree link = HtmlTree.LINK("stylesheet", "text/css", + pathToRoot.resolve(stylesheet).getPath(), + "Style"); + return link; + } + + protected Comment getGeneratedBy(boolean timestamp) { + String text = "Generated by javadoc"; // marker string, deliberately not localized + if (timestamp) { + Calendar calendar = new GregorianCalendar(TimeZone.getDefault()); + Date today = calendar.getTime(); + text += " ("+ configuration.getDocletSpecificBuildDate() + ") on " + today; + } + return new Comment(text); + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlDocument.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlDocument.java new file mode 100644 index 00000000000..8f995c7c772 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlDocument.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html.markup; + +import java.io.IOException; +import java.io.Writer; +import java.util.*; + +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; + +/** + * Class for generating an HTML document for javadoc output. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Bhavesh Patel + */ +public class HtmlDocument extends Content { + + private List docContent = Collections.emptyList(); + + /** + * Constructor to construct an HTML document. + * + * @param docType document type for the HTML document + * @param docComment comment for the document + * @param htmlTree HTML tree of the document + */ + public HtmlDocument(Content docType, Content docComment, Content htmlTree) { + docContent = new ArrayList<>(); + addContent(nullCheck(docType)); + addContent(nullCheck(docComment)); + addContent(nullCheck(htmlTree)); + } + + /** + * Constructor to construct an HTML document. + * + * @param docType document type for the HTML document + * @param htmlTree HTML tree of the document + */ + public HtmlDocument(Content docType, Content htmlTree) { + docContent = new ArrayList<>(); + addContent(nullCheck(docType)); + addContent(nullCheck(htmlTree)); + } + + /** + * Adds content for the HTML document. + * + * @param htmlContent html content to be added + */ + public final void addContent(Content htmlContent) { + if (htmlContent.isValid()) + docContent.add(htmlContent); + } + + /** + * This method is not supported by the class. + * + * @param stringContent string content that needs to be added + * @throws DocletAbortException this method will always throw a + * DocletAbortException because it + * is not supported. + */ + public void addContent(String stringContent) { + throw new DocletAbortException("not supported"); + } + + /** + * {@inheritDoc} + */ + public boolean isEmpty() { + return (docContent.isEmpty()); + } + + /** + * {@inheritDoc} + */ + public boolean write(Writer out, boolean atNewline) throws IOException { + for (Content c : docContent) + atNewline = c.write(out, atNewline); + return atNewline; + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlStyle.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlStyle.java new file mode 100644 index 00000000000..ccaf4769276 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlStyle.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html.markup; + +/** + * Enum representing HTML styles. The name map to values in the CSS file. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Bhavesh Patel + */ +public enum HtmlStyle { + aboutLanguage, + activeTableTab, + altColor, + bar, + block, + blockList, + blockListLast, + bottomNav, + circle, + classUseContainer, + colFirst, + colLast, + colOne, + constantsSummary, + constantValuesContainer, + contentContainer, + deprecatedContent, + deprecatedLabel, + deprecatedSummary, + deprecationComment, + description, + descfrmTypeLabel, + details, + docSummary, + emphasizedPhrase, + fixedNav, + header, + horizontal, + footer, + indexContainer, + indexNav, + inheritance, + interfaceName, + leftContainer, + leftTop, + leftBottom, + legalCopy, + mainContainer, + memberNameLabel, + memberNameLink, + memberSummary, + nameValue, + navBarCell1Rev, + navList, + navListSearch, + overrideSpecifyLabel, + overviewSummary, + packageHierarchyLabel, + paramLabel, + returnLabel, + rightContainer, + rightIframe, + rowColor, + searchTagLink, + seeLabel, + serializedFormContainer, + simpleTagLabel, + skipNav, + sourceContainer, + sourceLineNo, + subNav, + subNavList, + subTitle, + summary, + tabEnd, + tableTab, + throwsLabel, + title, + topNav, + typeNameLabel, + typeNameLink, + typeSummary, + useSummary +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlTag.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlTag.java new file mode 100644 index 00000000000..3bc64ea959f --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlTag.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html.markup; + +import jdk.javadoc.internal.doclets.toolkit.util.Utils; + +/** + * Enum representing HTML tags. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Bhavesh Patel + */ +public enum HtmlTag { + A(BlockType.INLINE, EndTag.END), + BLOCKQUOTE, + BODY(BlockType.OTHER, EndTag.END), + BR(BlockType.INLINE, EndTag.NOEND), + CAPTION, + CENTER(HtmlVersion.HTML4), + CODE(BlockType.INLINE, EndTag.END), + DD, + DIR(HtmlVersion.HTML4), + DIV, + DL, + DT, + EM(BlockType.INLINE, EndTag.END), + FONT(HtmlVersion.HTML4, BlockType.INLINE, EndTag.END), + FOOTER(HtmlVersion.HTML5), + H1, + H2, + H3, + H4, + H5, + H6, + HEAD(BlockType.OTHER, EndTag.END), + HEADER(HtmlVersion.HTML5), + HR(BlockType.BLOCK, EndTag.NOEND), + HTML(BlockType.OTHER, EndTag.END), + I(BlockType.INLINE, EndTag.END), + IFRAME(BlockType.OTHER, EndTag.END), + IMG(BlockType.INLINE, EndTag.NOEND), + INPUT(BlockType.BLOCK, EndTag.NOEND), + LI, + LISTING, + LINK(BlockType.OTHER, EndTag.NOEND), + MAIN(HtmlVersion.HTML5), + MENU, + META(BlockType.OTHER, EndTag.NOEND), + NAV(HtmlVersion.HTML5), + NOSCRIPT(BlockType.OTHER, EndTag.END), + OL, + P, + PRE, + SCRIPT(BlockType.OTHER, EndTag.END), + SECTION(HtmlVersion.HTML5), + SMALL(BlockType.INLINE, EndTag.END), + SPAN(BlockType.INLINE, EndTag.END), + STRONG(BlockType.INLINE, EndTag.END), + SUB(BlockType.INLINE, EndTag.END), + TABLE, + TBODY, + TD, + TH, + TITLE(BlockType.OTHER, EndTag.END), + TR, + TT(HtmlVersion.HTML4, BlockType.INLINE, EndTag.END), + UL; + + public final BlockType blockType; + public final EndTag endTag; + public final String value; + public final HtmlVersion htmlVersion; + + /** + * Enum representing the type of HTML element. + */ + public static enum BlockType { + BLOCK, + INLINE, + OTHER + } + + /** + * Enum representing HTML end tag requirement. + */ + public static enum EndTag { + END, + NOEND + } + + HtmlTag() { + this(HtmlVersion.ALL, BlockType.BLOCK, EndTag.END); + } + + HtmlTag(HtmlVersion htmlVersion) { + this(htmlVersion, BlockType.BLOCK, EndTag.END); + } + + HtmlTag(BlockType blockType, EndTag endTag ) { + this(HtmlVersion.ALL, blockType, endTag); + } + + HtmlTag(HtmlVersion htmlVersion, BlockType blockType, EndTag endTag ) { + this.htmlVersion = htmlVersion; + this.blockType = blockType; + this.endTag = endTag; + this.value = Utils.toLowerCase(name()); + } + + /** + * Returns true if the end tag is required. This is specific to the standard + * doclet and does not exactly resemble the W3C specifications. + * + * @return true if end tag needs to be displayed else return false + */ + public boolean endTagRequired() { + return (endTag == EndTag.END); + } + + /** + * Returns true if the tag is allowed in the output HTML version of this javadoc run. + * + * @param htmlVer the output HTML version for this javadoc run + * @return true if the tag is allowed + */ + public boolean allowTag(HtmlVersion htmlVer) { + return (this.htmlVersion == HtmlVersion.ALL || this.htmlVersion == htmlVer); + } + + public String toString() { + return value; + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlTree.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlTree.java new file mode 100644 index 00000000000..93b9071dcf7 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlTree.java @@ -0,0 +1,978 @@ +/* + * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html.markup; + +import java.io.IOException; +import java.io.Writer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import jdk.javadoc.internal.doclets.formats.html.markup.HtmlAttr.Role; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.DocletConstants; + +/** + * Class for generating HTML tree for javadoc output. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Bhavesh Patel + */ +public class HtmlTree extends Content { + + private HtmlTag htmlTag; + private Map attrs = Collections.emptyMap(); + private List content = Collections.emptyList(); + public static final Content EMPTY = new StringContent(""); + + /** + * Constructor to construct HtmlTree object. + * + * @param tag HTML tag for the HtmlTree object + */ + public HtmlTree(HtmlTag tag) { + htmlTag = nullCheck(tag); + } + + /** + * Constructor to construct HtmlTree object. + * + * @param tag HTML tag for the HtmlTree object + * @param contents contents to be added to the tree + */ + public HtmlTree(HtmlTag tag, Content... contents) { + this(tag); + for (Content content: contents) + addContent(content); + } + + /** + * Adds an attribute for the HTML tag. + * + * @param attrName name of the attribute + * @param attrValue value of the attribute + */ + public void addAttr(HtmlAttr attrName, String attrValue) { + if (attrs.isEmpty()) + attrs = new LinkedHashMap<>(3); + attrs.put(nullCheck(attrName), escapeHtmlChars(attrValue)); + } + + public void setTitle(Content body) { + addAttr(HtmlAttr.TITLE, stripHtml(body)); + } + + public void setRole(Role role) { + addAttr(HtmlAttr.ROLE, role.toString()); + } + + /** + * Adds a style for the HTML tag. + * + * @param style style to be added + */ + public void addStyle(HtmlStyle style) { + addAttr(HtmlAttr.CLASS, style.toString()); + } + + /** + * Adds content for the HTML tag. + * + * @param tagContent tag content to be added + */ + public void addContent(Content tagContent) { + if (tagContent instanceof ContentBuilder) { + for (Content content: ((ContentBuilder)tagContent).contents) { + addContent(content); + } + } + else if (tagContent == HtmlTree.EMPTY || tagContent.isValid()) { + if (content.isEmpty()) + content = new ArrayList<>(); + content.add(tagContent); + } + } + + /** + * This method adds a string content to the htmltree. If the last content member + * added is a StringContent, append the string to that StringContent or else + * create a new StringContent and add it to the html tree. + * + * @param stringContent string content that needs to be added + */ + public void addContent(String stringContent) { + if (!content.isEmpty()) { + Content lastContent = content.get(content.size() - 1); + if (lastContent instanceof StringContent) + lastContent.addContent(stringContent); + else + addContent(new StringContent(stringContent)); + } + else + addContent(new StringContent(stringContent)); + } + + public int charCount() { + int n = 0; + for (Content c : content) + n += c.charCount(); + return n; + } + + /** + * Given a string, escape all special html characters and + * return the result. + * + * @param s The string to check. + * @return the original string with all of the HTML characters escaped. + */ + private static String escapeHtmlChars(String s) { + for (int i = 0; i < s.length(); i++) { + char ch = s.charAt(i); + switch (ch) { + // only start building a new string if we need to + case '<': case '>': case '&': + StringBuilder sb = new StringBuilder(s.substring(0, i)); + for ( ; i < s.length(); i++) { + ch = s.charAt(i); + switch (ch) { + case '<': sb.append("<"); break; + case '>': sb.append(">"); break; + case '&': sb.append("&"); break; + default: sb.append(ch); break; + } + } + return sb.toString(); + } + } + return s; + } + + /** + * A set of ASCII URI characters to be left unencoded. + */ + public static final BitSet NONENCODING_CHARS = new BitSet(256); + + static { + // alphabetic characters + for (int i = 'a'; i <= 'z'; i++) { + NONENCODING_CHARS.set(i); + } + for (int i = 'A'; i <= 'Z'; i++) { + NONENCODING_CHARS.set(i); + } + // numeric characters + for (int i = '0'; i <= '9'; i++) { + NONENCODING_CHARS.set(i); + } + // Reserved characters as per RFC 3986. These are set of delimiting characters. + String noEnc = ":/?#[]@!$&'()*+,;="; + // Unreserved characters as per RFC 3986 which should not be percent encoded. + noEnc += "-._~"; + for (int i = 0; i < noEnc.length(); i++) { + NONENCODING_CHARS.set(noEnc.charAt(i)); + } + } + + private static String encodeURL(String url) { + StringBuilder sb = new StringBuilder(); + for (byte c : url.getBytes(Charset.forName("UTF-8"))) { + if (NONENCODING_CHARS.get(c & 0xFF)) { + sb.append((char) c); + } else { + sb.append(String.format("%%%02X", c & 0xFF)); + } + } + return sb.toString(); + } + + /** + * Generates an HTML anchor tag. + * + * @param ref reference url for the anchor tag + * @param body content for the anchor tag + * @return an HtmlTree object + */ + public static HtmlTree A(String ref, Content body) { + HtmlTree htmltree = new HtmlTree(HtmlTag.A, nullCheck(body)); + htmltree.addAttr(HtmlAttr.HREF, encodeURL(ref)); + return htmltree; + } + + /** + * Generates an HTML anchor tag with an id or a name attribute and content. + * + * @param htmlVersion the version of the generated HTML + * @param attr name or id attribute for the anchor tag + * @param body content for the anchor tag + * @return an HtmlTree object + */ + public static HtmlTree A(HtmlVersion htmlVersion, String attr, Content body) { + HtmlTree htmltree = new HtmlTree(HtmlTag.A); + htmltree.addAttr((htmlVersion == HtmlVersion.HTML4) + ? HtmlAttr.NAME + : HtmlAttr.ID, + nullCheck(attr)); + htmltree.addContent(nullCheck(body)); + return htmltree; + } + + /** + * Generates an HTML anchor tag with id attribute and a body. + * + * @param id id for the anchor tag + * @param body body for the anchor tag + * @return an HtmlTree object + */ + public static HtmlTree A_ID(String id, Content body) { + HtmlTree htmltree = new HtmlTree(HtmlTag.A); + htmltree.addAttr(HtmlAttr.ID, nullCheck(id)); + htmltree.addContent(nullCheck(body)); + return htmltree; + } + + /** + * Generates a CAPTION tag with some content. + * + * @param body content for the tag + * @return an HtmlTree object for the CAPTION tag + */ + public static HtmlTree CAPTION(Content body) { + HtmlTree htmltree = new HtmlTree(HtmlTag.CAPTION, nullCheck(body)); + return htmltree; + } + + /** + * Generates a CODE tag with some content. + * + * @param body content for the tag + * @return an HtmlTree object for the CODE tag + */ + public static HtmlTree CODE(Content body) { + HtmlTree htmltree = new HtmlTree(HtmlTag.CODE, nullCheck(body)); + return htmltree; + } + + /** + * Generates a DD tag with some content. + * + * @param body content for the tag + * @return an HtmlTree object for the DD tag + */ + public static HtmlTree DD(Content body) { + HtmlTree htmltree = new HtmlTree(HtmlTag.DD, nullCheck(body)); + return htmltree; + } + + /** + * Generates a DL tag with some content. + * + * @param body content for the tag + * @return an HtmlTree object for the DL tag + */ + public static HtmlTree DL(Content body) { + HtmlTree htmltree = new HtmlTree(HtmlTag.DL, nullCheck(body)); + return htmltree; + } + + /** + * Generates a DIV tag with the style class attributes. It also encloses + * a content. + * + * @param styleClass stylesheet class for the tag + * @param body content for the tag + * @return an HtmlTree object for the DIV tag + */ + public static HtmlTree DIV(HtmlStyle styleClass, Content body) { + HtmlTree htmltree = new HtmlTree(HtmlTag.DIV, nullCheck(body)); + if (styleClass != null) + htmltree.addStyle(styleClass); + return htmltree; + } + + /** + * Generates a DIV tag with some content. + * + * @param body content for the tag + * @return an HtmlTree object for the DIV tag + */ + public static HtmlTree DIV(Content body) { + return DIV(null, body); + } + + /** + * Generates a DT tag with some content. + * + * @param body content for the tag + * @return an HtmlTree object for the DT tag + */ + public static HtmlTree DT(Content body) { + HtmlTree htmltree = new HtmlTree(HtmlTag.DT, nullCheck(body)); + return htmltree; + } + + /** + * Generates a FOOTER tag with role attribute. + * + * @return an HtmlTree object for the FOOTER tag + */ + public static HtmlTree FOOTER() { + HtmlTree htmltree = new HtmlTree(HtmlTag.FOOTER); + htmltree.setRole(Role.CONTENTINFO); + return htmltree; + } + + /** + * Generates a HEADER tag with role attribute. + * + * @return an HtmlTree object for the HEADER tag + */ + public static HtmlTree HEADER() { + HtmlTree htmltree = new HtmlTree(HtmlTag.HEADER); + htmltree.setRole(Role.BANNER); + return htmltree; + } + + /** + * Generates a heading tag (h1 to h6) with the title and style class attributes. It also encloses + * a content. + * + * @param headingTag the heading tag to be generated + * @param printTitle true if title for the tag needs to be printed else false + * @param styleClass stylesheet class for the tag + * @param body content for the tag + * @return an HtmlTree object for the tag + */ + public static HtmlTree HEADING(HtmlTag headingTag, boolean printTitle, + HtmlStyle styleClass, Content body) { + HtmlTree htmltree = new HtmlTree(headingTag, nullCheck(body)); + if (printTitle) + htmltree.setTitle(body); + if (styleClass != null) + htmltree.addStyle(styleClass); + return htmltree; + } + + /** + * Generates a heading tag (h1 to h6) with style class attribute. It also encloses + * a content. + * + * @param headingTag the heading tag to be generated + * @param styleClass stylesheet class for the tag + * @param body content for the tag + * @return an HtmlTree object for the tag + */ + public static HtmlTree HEADING(HtmlTag headingTag, HtmlStyle styleClass, Content body) { + return HEADING(headingTag, false, styleClass, body); + } + + /** + * Generates a heading tag (h1 to h6) with the title attribute. It also encloses + * a content. + * + * @param headingTag the heading tag to be generated + * @param printTitle true if the title for the tag needs to be printed else false + * @param body content for the tag + * @return an HtmlTree object for the tag + */ + public static HtmlTree HEADING(HtmlTag headingTag, boolean printTitle, Content body) { + return HEADING(headingTag, printTitle, null, body); + } + + /** + * Generates a heading tag (h1 to h6) with some content. + * + * @param headingTag the heading tag to be generated + * @param body content for the tag + * @return an HtmlTree object for the tag + */ + public static HtmlTree HEADING(HtmlTag headingTag, Content body) { + return HEADING(headingTag, false, null, body); + } + + /** + * Generates an HTML tag with lang attribute. It also adds head and body + * content to the HTML tree. + * + * @param lang language for the HTML document + * @param head head for the HTML tag + * @param body body for the HTML tag + * @return an HtmlTree object for the HTML tag + */ + public static HtmlTree HTML(String lang, Content head, Content body) { + HtmlTree htmltree = new HtmlTree(HtmlTag.HTML, nullCheck(head), nullCheck(body)); + htmltree.addAttr(HtmlAttr.LANG, nullCheck(lang)); + return htmltree; + } + + /** + * Generates a IFRAME tag. + * + * @param src the url of the document to be shown in the frame + * @param name specifies the name of the frame + * @param title the title for the frame + * @return an HtmlTree object for the IFRAME tag + */ + public static HtmlTree IFRAME(String src, String name, String title) { + HtmlTree htmltree = new HtmlTree(HtmlTag.IFRAME); + htmltree.addAttr(HtmlAttr.SRC, nullCheck(src)); + htmltree.addAttr(HtmlAttr.NAME, nullCheck(name)); + htmltree.addAttr(HtmlAttr.TITLE, nullCheck(title)); + return htmltree; + } + + /** + * Generates a INPUT tag with some id. + * + * @param type the type of input + * @param id id for the tag + * @return an HtmlTree object for the INPUT tag + */ + public static HtmlTree INPUT(String type, String id) { + HtmlTree htmltree = new HtmlTree(HtmlTag.INPUT); + htmltree.addAttr(HtmlAttr.TYPE, nullCheck(type)); + htmltree.addAttr(HtmlAttr.ID, nullCheck(id)); + htmltree.addAttr(HtmlAttr.VALUE, " "); + htmltree.addAttr(HtmlAttr.DISABLED, "disabled"); + return htmltree; + } + + /** + * Generates a LI tag with some content. + * + * @param body content for the tag + * @return an HtmlTree object for the LI tag + */ + public static HtmlTree LI(Content body) { + return LI(null, body); + } + + /** + * Generates a LI tag with some content. + * + * @param styleClass style for the tag + * @param body content for the tag + * @return an HtmlTree object for the LI tag + */ + public static HtmlTree LI(HtmlStyle styleClass, Content body) { + HtmlTree htmltree = new HtmlTree(HtmlTag.LI, nullCheck(body)); + if (styleClass != null) + htmltree.addStyle(styleClass); + return htmltree; + } + + /** + * Generates a LINK tag with the rel, type, href and title attributes. + * + * @param rel relevance of the link + * @param type type of link + * @param href the path for the link + * @param title title for the link + * @return an HtmlTree object for the LINK tag + */ + public static HtmlTree LINK(String rel, String type, String href, String title) { + HtmlTree htmltree = new HtmlTree(HtmlTag.LINK); + htmltree.addAttr(HtmlAttr.REL, nullCheck(rel)); + htmltree.addAttr(HtmlAttr.TYPE, nullCheck(type)); + htmltree.addAttr(HtmlAttr.HREF, nullCheck(href)); + htmltree.addAttr(HtmlAttr.TITLE, nullCheck(title)); + return htmltree; + } + + /** + * Generates a MAIN tag with role attribute. + * + * @return an HtmlTree object for the MAIN tag + */ + public static HtmlTree MAIN() { + HtmlTree htmltree = new HtmlTree(HtmlTag.MAIN); + htmltree.setRole(Role.MAIN); + return htmltree; + } + + /** + * Generates a MAIN tag with role attribute and some content. + * + * @param body content of the MAIN tag + * @return an HtmlTree object for the MAIN tag + */ + public static HtmlTree MAIN(Content body) { + HtmlTree htmltree = new HtmlTree(HtmlTag.MAIN, nullCheck(body)); + htmltree.setRole(Role.MAIN); + return htmltree; + } + + /** + * Generates a MAIN tag with role attribute, style attribute and some content. + * + * @param styleClass style of the MAIN tag + * @param body content of the MAIN tag + * @return an HtmlTree object for the MAIN tag + */ + public static HtmlTree MAIN(HtmlStyle styleClass, Content body) { + HtmlTree htmltree = HtmlTree.MAIN(body); + if (styleClass != null) { + htmltree.addStyle(styleClass); + } + return htmltree; + } + + /** + * Generates a META tag with the http-equiv, content and charset attributes. + * + * @param httpEquiv http equiv attribute for the META tag + * @param content type of content + * @param charSet character set used + * @return an HtmlTree object for the META tag + */ + public static HtmlTree META(String httpEquiv, String content, String charSet) { + HtmlTree htmltree = new HtmlTree(HtmlTag.META); + String contentCharset = content + "; charset=" + charSet; + htmltree.addAttr(HtmlAttr.HTTP_EQUIV, nullCheck(httpEquiv)); + htmltree.addAttr(HtmlAttr.CONTENT, contentCharset); + return htmltree; + } + + /** + * Generates a META tag with the name and content attributes. + * + * @param name name attribute + * @param content type of content + * @return an HtmlTree object for the META tag + */ + public static HtmlTree META(String name, String content) { + HtmlTree htmltree = new HtmlTree(HtmlTag.META); + htmltree.addAttr(HtmlAttr.NAME, nullCheck(name)); + htmltree.addAttr(HtmlAttr.CONTENT, nullCheck(content)); + return htmltree; + } + + /** + * Generates a NAV tag with the role attribute. + * + * @return an HtmlTree object for the NAV tag + */ + public static HtmlTree NAV() { + HtmlTree htmltree = new HtmlTree(HtmlTag.NAV); + htmltree.setRole(Role.NAVIGATION); + return htmltree; + } + + /** + * Generates a NOSCRIPT tag with some content. + * + * @param body content of the noscript tag + * @return an HtmlTree object for the NOSCRIPT tag + */ + public static HtmlTree NOSCRIPT(Content body) { + HtmlTree htmltree = new HtmlTree(HtmlTag.NOSCRIPT, nullCheck(body)); + return htmltree; + } + + /** + * Generates a P tag with some content. + * + * @param body content of the Paragraph tag + * @return an HtmlTree object for the P tag + */ + public static HtmlTree P(Content body) { + return P(null, body); + } + + /** + * Generates a P tag with some content. + * + * @param styleClass style of the Paragraph tag + * @param body content of the Paragraph tag + * @return an HtmlTree object for the P tag + */ + public static HtmlTree P(HtmlStyle styleClass, Content body) { + HtmlTree htmltree = new HtmlTree(HtmlTag.P, nullCheck(body)); + if (styleClass != null) + htmltree.addStyle(styleClass); + return htmltree; + } + + /** + * Generates a SCRIPT tag with the type and src attributes. + * + * @param type type of link + * @param src the path for the script + * @return an HtmlTree object for the SCRIPT tag + */ + public static HtmlTree SCRIPT(String src) { + HtmlTree htmltree = HtmlTree.SCRIPT(); + htmltree.addAttr(HtmlAttr.SRC, nullCheck(src)); + return htmltree; + } + + /** + * Generates a SCRIPT tag with the type attribute. + * + * @return an HtmlTree object for the SCRIPT tag + */ + public static HtmlTree SCRIPT() { + HtmlTree htmltree = new HtmlTree(HtmlTag.SCRIPT); + htmltree.addAttr(HtmlAttr.TYPE, "text/javascript"); + return htmltree; + } + + /** + * Generates a SECTION tag with role attribute. + * + * @return an HtmlTree object for the SECTION tag + */ + public static HtmlTree SECTION() { + HtmlTree htmltree = new HtmlTree(HtmlTag.SECTION); + htmltree.setRole(Role.REGION); + return htmltree; + } + + /** + * Generates a SECTION tag with role attribute and some content. + * + * @param body content of the section tag + * @return an HtmlTree object for the SECTION tag + */ + public static HtmlTree SECTION(Content body) { + HtmlTree htmltree = new HtmlTree(HtmlTag.SECTION, nullCheck(body)); + htmltree.setRole(Role.REGION); + return htmltree; + } + + /** + * Generates a SMALL tag with some content. + * + * @param body content for the tag + * @return an HtmlTree object for the SMALL tag + */ + public static HtmlTree SMALL(Content body) { + HtmlTree htmltree = new HtmlTree(HtmlTag.SMALL, nullCheck(body)); + return htmltree; + } + + /** + * Generates a SPAN tag with some content. + * + * @param body content for the tag + * @return an HtmlTree object for the SPAN tag + */ + public static HtmlTree SPAN(Content body) { + return SPAN(null, body); + } + + /** + * Generates a SPAN tag with style class attribute and some content. + * + * @param styleClass style class for the tag + * @param body content for the tag + * @return an HtmlTree object for the SPAN tag + */ + public static HtmlTree SPAN(HtmlStyle styleClass, Content body) { + HtmlTree htmltree = new HtmlTree(HtmlTag.SPAN, nullCheck(body)); + if (styleClass != null) + htmltree.addStyle(styleClass); + return htmltree; + } + + /** + * Generates a SPAN tag with id and style class attributes. It also encloses + * a content. + * + * @param id the id for the tag + * @param styleClass stylesheet class for the tag + * @param body content for the tag + * @return an HtmlTree object for the SPAN tag + */ + public static HtmlTree SPAN(String id, HtmlStyle styleClass, Content body) { + HtmlTree htmltree = new HtmlTree(HtmlTag.SPAN, nullCheck(body)); + htmltree.addAttr(HtmlAttr.ID, nullCheck(id)); + if (styleClass != null) + htmltree.addStyle(styleClass); + return htmltree; + } + + /** + * Generates a Table tag with style class and summary attributes and some content. + * + * @param styleClass style of the table + * @param summary summary for the table + * @param body content for the table + * @return an HtmlTree object for the TABLE tag + */ + public static HtmlTree TABLE(HtmlStyle styleClass, String summary, Content body) { + HtmlTree htmltree = new HtmlTree(HtmlTag.TABLE, nullCheck(body)); + if (styleClass != null) + htmltree.addStyle(styleClass); + htmltree.addAttr(HtmlAttr.SUMMARY, nullCheck(summary)); + return htmltree; + } + + /** + * Generates a Table tag with style class attribute and some content. + * + * @param styleClass style of the table + * @param body content for the table + * @return an HtmlTree object for the TABLE tag + */ + public static HtmlTree TABLE(HtmlStyle styleClass, Content body) { + HtmlTree htmltree = new HtmlTree(HtmlTag.TABLE, nullCheck(body)); + if (styleClass != null) { + htmltree.addStyle(styleClass); + } + return htmltree; + } + + /** + * Generates a TD tag with style class attribute and some content. + * + * @param styleClass style for the tag + * @param body content for the tag + * @return an HtmlTree object for the TD tag + */ + public static HtmlTree TD(HtmlStyle styleClass, Content body) { + HtmlTree htmltree = new HtmlTree(HtmlTag.TD, nullCheck(body)); + if (styleClass != null) + htmltree.addStyle(styleClass); + return htmltree; + } + + /** + * Generates a TD tag for an HTML table with some content. + * + * @param body content for the tag + * @return an HtmlTree object for the TD tag + */ + public static HtmlTree TD(Content body) { + return TD(null, body); + } + + /** + * Generates a TH tag with style class and scope attributes and some content. + * + * @param styleClass style for the tag + * @param scope scope of the tag + * @param body content for the tag + * @return an HtmlTree object for the TH tag + */ + public static HtmlTree TH(HtmlStyle styleClass, String scope, Content body) { + HtmlTree htmltree = new HtmlTree(HtmlTag.TH, nullCheck(body)); + if (styleClass != null) + htmltree.addStyle(styleClass); + htmltree.addAttr(HtmlAttr.SCOPE, nullCheck(scope)); + return htmltree; + } + + /** + * Generates a TH tag with scope attribute and some content. + * + * @param scope scope of the tag + * @param body content for the tag + * @return an HtmlTree object for the TH tag + */ + public static HtmlTree TH(String scope, Content body) { + return TH(null, scope, body); + } + + /** + * Generates a TITLE tag with some content. + * + * @param body content for the tag + * @return an HtmlTree object for the TITLE tag + */ + public static HtmlTree TITLE(Content body) { + HtmlTree htmltree = new HtmlTree(HtmlTag.TITLE, nullCheck(body)); + return htmltree; + } + + /** + * Generates a TR tag for an HTML table with some content. + * + * @param body content for the tag + * @return an HtmlTree object for the TR tag + */ + public static HtmlTree TR(Content body) { + HtmlTree htmltree = new HtmlTree(HtmlTag.TR, nullCheck(body)); + return htmltree; + } + + /** + * Generates a UL tag with the style class attribute and some content. + * + * @param styleClass style for the tag + * @param body content for the tag + * @return an HtmlTree object for the UL tag + */ + public static HtmlTree UL(HtmlStyle styleClass, Content body) { + HtmlTree htmltree = new HtmlTree(HtmlTag.UL, nullCheck(body)); + htmltree.addStyle(nullCheck(styleClass)); + return htmltree; + } + + /** + * {@inheritDoc} + */ + public boolean isEmpty() { + return (!hasContent() && !hasAttrs()); + } + + /** + * Returns true if the HTML tree has content. + * + * @return true if the HTML tree has content else return false + */ + public boolean hasContent() { + return (!content.isEmpty()); + } + + /** + * Returns true if the HTML tree has attributes. + * + * @return true if the HTML tree has attributes else return false + */ + public boolean hasAttrs() { + return (!attrs.isEmpty()); + } + + /** + * Returns true if the HTML tree has a specific attribute. + * + * @param attrName name of the attribute to check within the HTML tree + * @return true if the HTML tree has the specified attribute else return false + */ + public boolean hasAttr(HtmlAttr attrName) { + return (attrs.containsKey(attrName)); + } + + /** + * Returns true if the HTML tree is valid. This check is more specific to + * standard doclet and not exactly similar to W3C specifications. But it + * ensures HTML validation. + * + * @return true if the HTML tree is valid + */ + public boolean isValid() { + switch (htmlTag) { + case A : + return (hasAttr(HtmlAttr.NAME) || hasAttr(HtmlAttr.ID) || (hasAttr(HtmlAttr.HREF) && hasContent())); + case BR : + return (!hasContent() && (!hasAttrs() || hasAttr(HtmlAttr.CLEAR))); + case IFRAME : + return (hasAttr(HtmlAttr.SRC) && !hasContent()); + case HR : + case INPUT: + return (!hasContent()); + case IMG : + return (hasAttr(HtmlAttr.SRC) && hasAttr(HtmlAttr.ALT) && !hasContent()); + case LINK : + return (hasAttr(HtmlAttr.HREF) && !hasContent()); + case META : + return (hasAttr(HtmlAttr.CONTENT) && !hasContent()); + case SCRIPT : + return ((hasAttr(HtmlAttr.TYPE) && hasAttr(HtmlAttr.SRC) && !hasContent()) || + (hasAttr(HtmlAttr.TYPE) && hasContent())); + default : + return hasContent(); + } + } + + /** + * Returns true if the element is an inline element. + * + * @return true if the HTML tag is an inline element + */ + public boolean isInline() { + return (htmlTag.blockType == HtmlTag.BlockType.INLINE); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean write(Writer out, boolean atNewline) throws IOException { + if (!isInline() && !atNewline) + out.write(DocletConstants.NL); + String tagString = htmlTag.toString(); + out.write("<"); + out.write(tagString); + Iterator iterator = attrs.keySet().iterator(); + HtmlAttr key; + String value; + while (iterator.hasNext()) { + key = iterator.next(); + value = attrs.get(key); + out.write(" "); + out.write(key.toString()); + if (!value.isEmpty()) { + out.write("=\""); + out.write(value); + out.write("\""); + } + } + out.write(">"); + boolean nl = false; + for (Content c : content) + nl = c.write(out, nl); + if (htmlTag.endTagRequired()) { + out.write(""); + } + if (!isInline()) { + out.write(DocletConstants.NL); + return true; + } else { + return false; + } + } + + /** + * Given a Content node, strips all html characters and + * return the result. + * + * @param body The content node to check. + * @return the plain text from the content node + * + */ + private static String stripHtml(Content body) { + String rawString = body.toString(); + // remove HTML tags + rawString = rawString.replaceAll("\\<.*?>", " "); + // consolidate multiple spaces between a word to a single space + rawString = rawString.replaceAll("\\b\\s{2,}\\b", " "); + // remove extra whitespaces + return rawString.trim(); + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlVersion.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlVersion.java new file mode 100644 index 00000000000..c3b3e6ded5e --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlVersion.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html.markup; + +/** + * Enum representing the version of HTML generated by javadoc. + * + * @author Bhavesh Patel + */ +public enum HtmlVersion { + HTML4, + HTML5, + ALL +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlWriter.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlWriter.java new file mode 100644 index 00000000000..589f342ef79 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlWriter.java @@ -0,0 +1,515 @@ +/* + * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html.markup; + +import java.io.*; +import java.util.*; + +import jdk.javadoc.internal.doclets.toolkit.Configuration; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.DocFile; +import jdk.javadoc.internal.doclets.toolkit.util.DocPath; +import jdk.javadoc.internal.doclets.toolkit.util.DocletConstants; +import jdk.javadoc.internal.doclets.toolkit.util.MethodTypes; + + +/** + * Class for the Html format code generation. + * Initializes PrintWriter with FileWriter, to enable print + * related methods to generate the code to the named File through FileWriter. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Atul M Dambalkar + * @author Bhavesh Patel (Modified) + */ +public class HtmlWriter { + + /** + * The window title of this file + */ + protected String winTitle; + + /** + * The configuration + */ + protected Configuration configuration; + + /** + * The flag to indicate whether a member details list is printed or not. + */ + protected boolean memberDetailsListPrinted; + + /** + * Header for tables displaying packages and description.. + */ + protected final List packageTableHeader; + + /** + * Summary for use tables displaying class and package use. + */ + protected final String useTableSummary; + + /** + * Column header for class docs displaying Modifier and Type header. + */ + protected final String modifierTypeHeader; + + public final Content overviewLabel; + + public final Content defaultPackageLabel; + + public final Content packageLabel; + + public final Content useLabel; + + public final Content prevLabel; + + public final Content nextLabel; + + public final Content prevclassLabel; + + public final Content nextclassLabel; + + public final Content summaryLabel; + + public final Content detailLabel; + + public final Content framesLabel; + + public final Content noframesLabel; + + public final Content treeLabel; + + public final Content classLabel; + + public final Content deprecatedLabel; + + public final Content deprecatedPhrase; + + public final Content allclassesLabel; + + public final Content allpackagesLabel; + + public final Content indexLabel; + + public final Content helpLabel; + + public final Content seeLabel; + + public final Content descriptionLabel; + + public final Content prevpackageLabel; + + public final Content nextpackageLabel; + + public final Content packagesLabel; + + public final Content methodDetailsLabel; + + public final Content annotationTypeDetailsLabel; + + public final Content fieldDetailsLabel; + + public final Content propertyDetailsLabel; + + public final Content constructorDetailsLabel; + + public final Content enumConstantsDetailsLabel; + + public final Content specifiedByLabel; + + public final Content overridesLabel; + + public final Content descfrmClassLabel; + + public final Content descfrmInterfaceLabel; + + private final Writer writer; + + protected Content script; + + + /** + * Constructor. + * + * @param path The directory path to be created for this file + * or null if none to be created. + * @exception IOException Exception raised by the FileWriter is passed on + * to next level. + * @exception UnsupportedEncodingException Exception raised by the + * OutputStreamWriter is passed on to next level. + */ + public HtmlWriter(Configuration configuration, DocPath path) + throws IOException, UnsupportedEncodingException { + writer = DocFile.createFileForOutput(configuration, path).openWriter(); + this.configuration = configuration; + this.memberDetailsListPrinted = false; + packageTableHeader = new ArrayList<>(); + packageTableHeader.add(configuration.getText("doclet.Package")); + packageTableHeader.add(configuration.getText("doclet.Description")); + useTableSummary = configuration.getText("doclet.Use_Table_Summary", + configuration.getText("doclet.packages")); + modifierTypeHeader = configuration.getText("doclet.0_and_1", + configuration.getText("doclet.Modifier"), + configuration.getText("doclet.Type")); + overviewLabel = getResource("doclet.Overview"); + defaultPackageLabel = new StringContent(DocletConstants.DEFAULT_PACKAGE_NAME); + packageLabel = getResource("doclet.Package"); + useLabel = getResource("doclet.navClassUse"); + prevLabel = getResource("doclet.Prev"); + nextLabel = getResource("doclet.Next"); + prevclassLabel = getNonBreakResource("doclet.Prev_Class"); + nextclassLabel = getNonBreakResource("doclet.Next_Class"); + summaryLabel = getResource("doclet.Summary"); + detailLabel = getResource("doclet.Detail"); + framesLabel = getResource("doclet.Frames"); + noframesLabel = getNonBreakResource("doclet.No_Frames"); + treeLabel = getResource("doclet.Tree"); + classLabel = getResource("doclet.Class"); + deprecatedLabel = getResource("doclet.navDeprecated"); + deprecatedPhrase = getResource("doclet.Deprecated"); + allclassesLabel = getNonBreakResource("doclet.All_Classes"); + allpackagesLabel = getNonBreakResource("doclet.All_Packages"); + indexLabel = getResource("doclet.Index"); + helpLabel = getResource("doclet.Help"); + seeLabel = getResource("doclet.See"); + descriptionLabel = getResource("doclet.Description"); + prevpackageLabel = getNonBreakResource("doclet.Prev_Package"); + nextpackageLabel = getNonBreakResource("doclet.Next_Package"); + packagesLabel = getResource("doclet.Packages"); + methodDetailsLabel = getResource("doclet.Method_Detail"); + annotationTypeDetailsLabel = getResource("doclet.Annotation_Type_Member_Detail"); + fieldDetailsLabel = getResource("doclet.Field_Detail"); + propertyDetailsLabel = getResource("doclet.Property_Detail"); + constructorDetailsLabel = getResource("doclet.Constructor_Detail"); + enumConstantsDetailsLabel = getResource("doclet.Enum_Constant_Detail"); + specifiedByLabel = getResource("doclet.Specified_By"); + overridesLabel = getResource("doclet.Overrides"); + descfrmClassLabel = getResource("doclet.Description_From_Class"); + descfrmInterfaceLabel = getResource("doclet.Description_From_Interface"); + } + + public void write(Content c) throws IOException { + c.write(writer, true); + } + + public void close() throws IOException { + writer.close(); + } + + /** + * Get the configuration string as a content. + * + * @param key the key to look for in the configuration file + * @return a content tree for the text + */ + public Content getResource(String key) { + return configuration.getResource(key); + } + + /** + * Get the configuration string as a content, replacing spaces + * with non-breaking spaces. + * + * @param key the key to look for in the configuration file + * @return a content tree for the text + */ + public Content getNonBreakResource(String key) { + String text = configuration.getText(key); + Content c = configuration.newContent(); + int start = 0; + int p; + while ((p = text.indexOf(" ", start)) != -1) { + c.addContent(text.substring(start, p)); + c.addContent(RawHtml.nbsp); + start = p + 1; + } + c.addContent(text.substring(start)); + return c; + } + + /** + * Get the configuration string as a content. + * + * @param key the key to look for in the configuration file + * @param o string or content argument added to configuration text + * @return a content tree for the text + */ + public Content getResource(String key, Object o) { + return configuration.getResource(key, o); + } + + /** + * Get the configuration string as a content. + * + * @param key the key to look for in the configuration file + * @param o1 string or content argument added to configuration text + * @param o2 string or content argument added to configuration text + * @return a content tree for the text + */ + public Content getResource(String key, Object o0, Object o1) { + return configuration.getResource(key, o0, o1); + } + + /** + * Returns an HtmlTree for the SCRIPT tag. + * + * @return an HtmlTree for the SCRIPT tag + */ + protected HtmlTree getWinTitleScript(){ + HtmlTree script = HtmlTree.SCRIPT(); + if(winTitle != null && winTitle.length() > 0) { + String scriptCode = "" + DocletConstants.NL; + RawHtml scriptContent = new RawHtml(scriptCode); + script.addContent(scriptContent); + } + return script; + } + + /** + * Returns a String with escaped special JavaScript characters. + * + * @param s String that needs to be escaped + * @return a valid escaped JavaScript string + */ + private static String escapeJavaScriptChars(String s) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < s.length(); i++) { + char ch = s.charAt(i); + switch (ch) { + case '\b': + sb.append("\\b"); + break; + case '\t': + sb.append("\\t"); + break; + case '\n': + sb.append("\\n"); + break; + case '\f': + sb.append("\\f"); + break; + case '\r': + sb.append("\\r"); + break; + case '"': + sb.append("\\\""); + break; + case '\'': + sb.append("\\\'"); + break; + case '\\': + sb.append("\\\\"); + break; + default: + if (ch < 32 || ch >= 127) { + sb.append(String.format("\\u%04X", (int)ch)); + } else { + sb.append(ch); + } + break; + } + } + return sb.toString(); + } + + /** + * Returns a content tree for the SCRIPT tag for the main page(index.html). + * + * @return a content for the SCRIPT tag + */ + protected Content getFramesJavaScript() { + HtmlTree script = HtmlTree.SCRIPT(); + String scriptCode = DocletConstants.NL + + " targetPage = \"\" + window.location.search;" + DocletConstants.NL + + " if (targetPage != \"\" && targetPage != \"undefined\")" + DocletConstants.NL + + " targetPage = targetPage.substring(1);" + DocletConstants.NL + + " if (targetPage.indexOf(\":\") != -1 || (targetPage != \"\" && !validURL(targetPage)))" + DocletConstants.NL + + " targetPage = \"undefined\";" + DocletConstants.NL + + " function validURL(url) {" + DocletConstants.NL + + " try {" + DocletConstants.NL + + " url = decodeURIComponent(url);" + DocletConstants.NL + + " }" + DocletConstants.NL + + " catch (error) {" + DocletConstants.NL + + " return false;" + DocletConstants.NL + + " }" + DocletConstants.NL + + " var pos = url.indexOf(\".html\");" + DocletConstants.NL + + " if (pos == -1 || pos != url.length - 5)" + DocletConstants.NL + + " return false;" + DocletConstants.NL + + " var allowNumber = false;" + DocletConstants.NL + + " var allowSep = false;" + DocletConstants.NL + + " var seenDot = false;" + DocletConstants.NL + + " for (var i = 0; i < url.length - 5; i++) {" + DocletConstants.NL + + " var ch = url.charAt(i);" + DocletConstants.NL + + " if ('a' <= ch && ch <= 'z' ||" + DocletConstants.NL + + " 'A' <= ch && ch <= 'Z' ||" + DocletConstants.NL + + " ch == '$' ||" + DocletConstants.NL + + " ch == '_' ||" + DocletConstants.NL + + " ch.charCodeAt(0) > 127) {" + DocletConstants.NL + + " allowNumber = true;" + DocletConstants.NL + + " allowSep = true;" + DocletConstants.NL + + " } else if ('0' <= ch && ch <= '9'" + DocletConstants.NL + + " || ch == '-') {" + DocletConstants.NL + + " if (!allowNumber)" + DocletConstants.NL + + " return false;" + DocletConstants.NL + + " } else if (ch == '/' || ch == '.') {" + DocletConstants.NL + + " if (!allowSep)" + DocletConstants.NL + + " return false;" + DocletConstants.NL + + " allowNumber = false;" + DocletConstants.NL + + " allowSep = false;" + DocletConstants.NL + + " if (ch == '.')" + DocletConstants.NL + + " seenDot = true;" + DocletConstants.NL + + " if (ch == '/' && seenDot)" + DocletConstants.NL + + " return false;" + DocletConstants.NL + + " } else {" + DocletConstants.NL + + " return false;"+ DocletConstants.NL + + " }" + DocletConstants.NL + + " }" + DocletConstants.NL + + " return true;" + DocletConstants.NL + + " }" + DocletConstants.NL; + RawHtml scriptContent = new RawHtml(scriptCode); + script.addContent(scriptContent); + return script; + } + + /** + * Returns an HtmlTree for the BODY tag. + * + * @param includeScript set true if printing windowtitle script + * @param title title for the window + * @return an HtmlTree for the BODY tag + */ + public HtmlTree getBody(boolean includeScript, String title) { + HtmlTree body = new HtmlTree(HtmlTag.BODY); + // Set window title string which is later printed + this.winTitle = title; + // Don't print windowtitle script for overview-frame, allclasses-frame + // and package-frame + if (includeScript) { + this.script = getWinTitleScript(); + body.addContent(script); + Content noScript = HtmlTree.NOSCRIPT( + HtmlTree.DIV(getResource("doclet.No_Script_Message"))); + body.addContent(noScript); + } + return body; + } + + /** + * Generated javascript variables for the document. + * + * @param typeMap map comprising of method and type relationship + * @param methodTypes set comprising of all methods types for this class + */ + public void generateMethodTypesScript(Map typeMap, + Set methodTypes) { + String sep = ""; + StringBuilder vars = new StringBuilder("var methods = {"); + for (Map.Entry entry : typeMap.entrySet()) { + vars.append(sep); + sep = ","; + vars.append("\"") + .append(entry.getKey()) + .append("\":") + .append(entry.getValue()); + } + vars.append("};").append(DocletConstants.NL); + sep = ""; + vars.append("var tabs = {"); + for (MethodTypes entry : methodTypes) { + vars.append(sep); + sep = ","; + vars.append(entry.value()) + .append(":") + .append("[") + .append("\"") + .append(entry.tabId()) + .append("\"") + .append(sep) + .append("\"") + .append(configuration.getText(entry.resourceKey())) + .append("\"]"); + } + vars.append("};") + .append(DocletConstants.NL); + addStyles(HtmlStyle.altColor, vars); + addStyles(HtmlStyle.rowColor, vars); + addStyles(HtmlStyle.tableTab, vars); + addStyles(HtmlStyle.activeTableTab, vars); + script.addContent(new RawHtml(vars.toString())); + } + + /** + * Adds javascript style variables to the document. + * + * @param style style to be added as a javascript variable + * @param vars variable string to which the style variable will be added + */ + public void addStyles(HtmlStyle style, StringBuilder vars) { + vars.append("var ").append(style).append(" = \"").append(style) + .append("\";").append(DocletConstants.NL); + } + + /** + * Returns an HtmlTree for the TITLE tag. + * + * @return an HtmlTree for the TITLE tag + */ + public HtmlTree getTitle() { + HtmlTree title = HtmlTree.TITLE(new StringContent(winTitle)); + return title; + } + + public String codeText(String text) { + return "" + text + ""; + } + + /** + * Return "&nbsp;", non-breaking space. + */ + public Content getSpace() { + return RawHtml.nbsp; + } + + /* + * Returns a header for Modifier and Type column of a table. + */ + public String getModifierTypeHeader() { + return modifierTypeHeader; + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/RawHtml.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/RawHtml.java new file mode 100644 index 00000000000..fe425dd743a --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/RawHtml.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html.markup; + +import java.io.IOException; +import java.io.Writer; + +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; +import jdk.javadoc.internal.doclets.toolkit.util.DocletConstants; + +/** + * Class for generating raw HTML content to be added to HTML pages of javadoc output. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Bhavesh Patel + */ +public class RawHtml extends Content { + + private String rawHtmlContent; + + public static final Content nbsp = new RawHtml(" "); + + /** + * Constructor to construct a RawHtml object. + * + * @param rawHtml raw HTML text to be added + */ + public RawHtml(String rawHtml) { + rawHtmlContent = nullCheck(rawHtml); + } + + /** + * This method is not supported by the class. + * + * @param content content that needs to be added + * @throws DocletAbortException this method will always throw a + * DocletAbortException because it + * is not supported. + */ + public void addContent(Content content) { + throw new DocletAbortException("not supported"); + } + + /** + * This method is not supported by the class. + * + * @param stringContent string content that needs to be added + * @throws DocletAbortException this method will always throw a + * DocletAbortException because it + * is not supported. + */ + public void addContent(String stringContent) { + throw new DocletAbortException("not supported"); + } + + /** + * {@inheritDoc} + */ + public boolean isEmpty() { + return rawHtmlContent.isEmpty(); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return rawHtmlContent; + } + + private enum State { TEXT, ENTITY, TAG, STRING } + + @Override + public int charCount() { + return charCount(rawHtmlContent); + } + + static int charCount(String htmlText) { + State state = State.TEXT; + int count = 0; + for (int i = 0; i < htmlText.length(); i++) { + char c = htmlText.charAt(i); + switch (state) { + case TEXT: + switch (c) { + case '<': + state = State.TAG; + break; + case '&': + state = State.ENTITY; + count++; + break; + default: + count++; + } + break; + + case ENTITY: + if (!Character.isLetterOrDigit(c)) + state = State.TEXT; + break; + + case TAG: + switch (c) { + case '"': + state = State.STRING; + break; + case '>': + state = State.TEXT; + break; + } + break; + + case STRING: + switch (c) { + case '"': + state = State.TAG; + break; + } + } + } + return count; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean write(Writer out, boolean atNewline) throws IOException { + out.write(rawHtmlContent); + return rawHtmlContent.endsWith(DocletConstants.NL); + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/StringContent.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/StringContent.java new file mode 100644 index 00000000000..6b7c7dde2ed --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/StringContent.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.formats.html.markup; + +import java.io.IOException; +import java.io.Writer; + +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.DocletAbortException; +import jdk.javadoc.internal.doclets.toolkit.util.DocletConstants; + +/** + * Class for generating string content for HTML tags of javadoc output. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + * + * @author Bhavesh Patel + */ +public class StringContent extends Content { + + private StringBuilder stringContent; + + /** + * Constructor to construct StringContent object. + */ + public StringContent() { + stringContent = new StringBuilder(); + } + + /** + * Constructor to construct StringContent object with some initial content. + * + * @param initialContent initial content for the object + */ + public StringContent(String initialContent) { + stringContent = new StringBuilder(); + appendChars(initialContent); + } + + /** + * This method is not supported by the class. + * + * @param content content that needs to be added + * @throws DocletAbortException this method will always throw a + * DocletAbortException because it + * is not supported. + */ + @Override + public void addContent(Content content) { + throw new DocletAbortException("not supported"); + } + + /** + * Adds content for the StringContent object. The method escapes + * HTML characters for the string content that is added. + * + * @param strContent string content to be added + */ + @Override + public void addContent(String strContent) { + appendChars(strContent); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isEmpty() { + return (stringContent.length() == 0); + } + + @Override + public int charCount() { + return RawHtml.charCount(stringContent.toString()); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return stringContent.toString(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean write(Writer out, boolean atNewline) throws IOException { + String s = stringContent.toString(); + out.write(s); + return s.endsWith(DocletConstants.NL); + } + + private void appendChars(String s) { + for (int i = 0; i < s.length(); i++) { + char ch = s.charAt(i); + switch (ch) { + case '<': stringContent.append("<"); break; + case '>': stringContent.append(">"); break; + case '&': stringContent.append("&"); break; + default: stringContent.append(ch); break; + } + } + } +} diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/package-info.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/package-info.java new file mode 100644 index 00000000000..41069a3939c --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + This package contains classes that write HTML markup tags. + +

This is NOT part of any supported API. + If you write code that depends on this, you do so at your own risk. + This code and its internal interfaces are subject to change or + deletion without notice. + */ + +package jdk.javadoc.internal.doclets.formats.html.markup; diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/package-info.java b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/package-info.java new file mode 100644 index 00000000000..6219b40bdc2 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/package-info.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * This is the default HTML doclet provided with the JDK. + * + *

+ * This is NOT part of any supported API. If you write code that depends on this, you do so at + * your own risk. This code and its internal interfaces are subject to change or deletion without + * notice. + */ +package jdk.javadoc.internal.doclets.formats.html; diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/glass.png b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/glass.png new file mode 100644 index 00000000000..a7f591f467a Binary files /dev/null and b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/glass.png differ diff --git a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/jquery/external/jquery/jquery.js b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/jquery/external/jquery/jquery.js new file mode 100644 index 00000000000..c5c648255c1 --- /dev/null +++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/jquery/external/jquery/jquery.js @@ -0,0 +1,9789 @@ +/*! + * jQuery JavaScript Library v1.10.2 + * http://jquery.com/ + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * + * Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2013-07-03T13:48Z + */ +(function( window, undefined ) { + +// Can't do this because several apps including ASP.NET trace +// the stack via arguments.caller.callee and Firefox dies if +// you try to trace through "use strict" call chains. (#13335) +// Support: Firefox 18+ +//"use strict"; +var + // The deferred used on DOM ready + readyList, + + // A central reference to the root jQuery(document) + rootjQuery, + + // Support: IE<10 + // For `typeof xmlNode.method` instead of `xmlNode.method !== undefined` + core_strundefined = typeof undefined, + + // Use the correct document accordingly with window argument (sandbox) + location = window.location, + document = window.document, + docElem = document.documentElement, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // [[Class]] -> type pairs + class2type = {}, + + // List of deleted data cache ids, so we can reuse them + core_deletedIds = [], + + core_version = "1.10.2", + + // Save a reference to some core methods + core_concat = core_deletedIds.concat, + core_push = core_deletedIds.push, + core_slice = core_deletedIds.slice, + core_indexOf = core_deletedIds.indexOf, + core_toString = class2type.toString, + core_hasOwn = class2type.hasOwnProperty, + core_trim = core_version.trim, + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context, rootjQuery ); + }, + + // Used for matching numbers + core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source, + + // Used for splitting on whitespace + core_rnotwhite = /\S+/g, + + // Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE) + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g, + rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([\da-z])/gi, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }, + + // The ready event handler + completed = function( event ) { + + // readyState === "complete" is good enough for us to call the dom ready in oldIE + if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) { + detach(); + jQuery.ready(); + } + }, + // Clean-up method for dom ready events + detach = function() { + if ( document.addEventListener ) { + document.removeEventListener( "DOMContentLoaded", completed, false ); + window.removeEventListener( "load", completed, false ); + + } else { + document.detachEvent( "onreadystatechange", completed ); + window.detachEvent( "onload", completed ); + } + }; + +jQuery.fn = jQuery.prototype = { + // The current version of jQuery being used + jquery: core_version, + + constructor: jQuery, + init: function( selector, context, rootjQuery ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + + // scripts is true for back-compat + jQuery.merge( this, jQuery.parseHTML( + match[1], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return core_slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[ this.length + num ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + ret.context = this.context; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Add the callback + jQuery.ready.promise().done( fn ); + + return this; + }, + + slice: function() { + return this.pushStack( core_slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: core_push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var src, copyIsArray, copy, name, options, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ), + + noConflict: function( deep ) { + if ( window.$ === jQuery ) { + window.$ = _$; + } + + if ( deep && window.jQuery === jQuery ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger("ready").off("ready"); + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + isWindow: function( obj ) { + /* jshint eqeqeq: false */ + return obj != null && obj == obj.window; + }, + + isNumeric: function( obj ) { + return !isNaN( parseFloat(obj) ) && isFinite( obj ); + }, + + type: function( obj ) { + if ( obj == null ) { + return String( obj ); + } + return typeof obj === "object" || typeof obj === "function" ? + class2type[ core_toString.call(obj) ] || "object" : + typeof obj; + }, + + isPlainObject: function( obj ) { + var key; + + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + try { + // Not own constructor property must be Object + if ( obj.constructor && + !core_hasOwn.call(obj, "constructor") && + !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + } catch ( e ) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Support: IE<9 + // Handle iteration over inherited properties before own properties. + if ( jQuery.support.ownLast ) { + for ( key in obj ) { + return core_hasOwn.call( obj, key ); + } + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + for ( key in obj ) {} + + return key === undefined || core_hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + var name; + for ( name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw new Error( msg ); + }, + + // data: string of html + // context (optional): If specified, the fragment will be created in this context, defaults to document + // keepScripts (optional): If true, will include scripts passed in the html string + parseHTML: function( data, context, keepScripts ) { + if ( !data || typeof data !== "string" ) { + return null; + } + if ( typeof context === "boolean" ) { + keepScripts = context; + context = false; + } + context = context || document; + + var parsed = rsingleTag.exec( data ), + scripts = !keepScripts && []; + + // Single tag + if ( parsed ) { + return [ context.createElement( parsed[1] ) ]; + } + + parsed = jQuery.buildFragment( [ data ], context, scripts ); + if ( scripts ) { + jQuery( scripts ).remove(); + } + return jQuery.merge( [], parsed.childNodes ); + }, + + parseJSON: function( data ) { + // Attempt to parse using the native JSON parser first + if ( window.JSON && window.JSON.parse ) { + return window.JSON.parse( data ); + } + + if ( data === null ) { + return data; + } + + if ( typeof data === "string" ) { + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + if ( data ) { + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test( data.replace( rvalidescape, "@" ) + .replace( rvalidtokens, "]" ) + .replace( rvalidbraces, "")) ) { + + return ( new Function( "return " + data ) )(); + } + } + } + + jQuery.error( "Invalid JSON: " + data ); + }, + + // Cross-browser xml parsing + parseXML: function( data ) { + var xml, tmp; + if ( !data || typeof data !== "string" ) { + return null; + } + try { + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + } catch( e ) { + xml = undefined; + } + if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; + }, + + noop: function() {}, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && jQuery.trim( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + }, + + // args is for internal usage only + each: function( obj, callback, args ) { + var value, + i = 0, + length = obj.length, + isArray = isArraylike( obj ); + + if ( args ) { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } + } + + return obj; + }, + + // Use native String.trim function wherever possible + trim: core_trim && !core_trim.call("\uFEFF\xA0") ? + function( text ) { + return text == null ? + "" : + core_trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArraylike( Object(arr) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + core_push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + var len; + + if ( arr ) { + if ( core_indexOf ) { + return core_indexOf.call( arr, elem, i ); + } + + len = arr.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in arr && arr[ i ] === elem ) { + return i; + } + } + } + + return -1; + }, + + merge: function( first, second ) { + var l = second.length, + i = first.length, + j = 0; + + if ( typeof l === "number" ) { + for ( ; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var retVal, + ret = [], + i = 0, + length = elems.length; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, + i = 0, + length = elems.length, + isArray = isArraylike( elems ), + ret = []; + + // Go through the array, translating each of the items to their + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + } + + // Flatten any nested arrays + return core_concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var args, proxy, tmp; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = core_slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + // Multifunctional method to get and set values of a collection + // The value/s can optionally be executed if it's a function + access: function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + length = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < length; i++ ) { + fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); + } + } + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + length ? fn( elems[0], key ) : emptyGet; + }, + + now: function() { + return ( new Date() ).getTime(); + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations. + // Note: this method belongs to the css module but it's needed here for the support module. + // If support gets modularized, this method should be moved back to the css module. + swap: function( elem, options, callback, args ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.apply( elem, args || [] ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; + } +}); + +jQuery.ready.promise = function( obj ) { + if ( !readyList ) { + + readyList = jQuery.Deferred(); + + // Catch cases where $(document).ready() is called after the browser event has already occurred. + // we once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout( jQuery.ready ); + + // Standards-based browsers support DOMContentLoaded + } else if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed, false ); + + // If IE event model is used + } else { + // Ensure firing before onload, maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", completed ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", completed ); + + // If IE and not a frame + // continually check to see if the document is ready + var top = false; + + try { + top = window.frameElement == null && document.documentElement; + } catch(e) {} + + if ( top && top.doScroll ) { + (function doScrollCheck() { + if ( !jQuery.isReady ) { + + try { + // Use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + top.doScroll("left"); + } catch(e) { + return setTimeout( doScrollCheck, 50 ); + } + + // detach all dom ready events + detach(); + + // and execute any waiting functions + jQuery.ready(); + } + })(); + } + } + } + return readyList.promise( obj ); +}; + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +function isArraylike( obj ) { + var length = obj.length, + type = jQuery.type( obj ); + + if ( jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === "array" || type !== "function" && + ( length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj ); +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); +/*! + * Sizzle CSS Selector Engine v1.10.2 + * http://sizzlejs.com/ + * + * Copyright 2013 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2013-07-03 + */ +(function( window, undefined ) { + +var i, + support, + cachedruns, + Expr, + getText, + isXML, + compile, + outermostContext, + sortInput, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + -(new Date()), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + hasDuplicate = false, + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + return 0; + } + return 0; + }, + + // General-purpose constants + strundefined = typeof undefined, + MAX_NEGATIVE = 1 << 31, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf if we can't use a native one + indexOf = arr.indexOf || function( elem ) { + var i = 0, + len = this.length; + for ( ; i < len; i++ ) { + if ( this[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + // http://www.w3.org/TR/css3-syntax/#characters + characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", + + // Loosely modeled on CSS identifier characters + // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors + // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = characterEncoding.replace( "w", "w#" ), + + // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace + + "*(?:([*^$|!~]?=)" + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]", + + // Prefer arguments quoted, + // then not containing pseudos/brackets, + // then attribute selectors/non-parenthetical expressions, + // then anything else + // These preferences are here to reduce the number of selectors + // needing tokenize in the PSEUDO preFilter + pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + + rsibling = new RegExp( whitespace + "*[+~]" ), + rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*)" + whitespace + "*\\]", "g" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + characterEncoding + ")" ), + "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), + "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rescape = /'|\\/g, + + // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + // BMP codepoint + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }; + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var match, elem, m, nodeType, + // QSA vars + i, groups, old, nid, newContext, newSelector; + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + + context = context || document; + results = results || []; + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { + return []; + } + + if ( documentIsHTML && !seed ) { + + // Shortcuts + if ( (match = rquickExpr.exec( selector )) ) { + // Speed-up: Sizzle("#ID") + if ( (m = match[1]) ) { + if ( nodeType === 9 ) { + elem = context.getElementById( m ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE, Opera, and Webkit return items + // by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + } else { + // Context is not a document + if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && + contains( context, elem ) && elem.id === m ) { + results.push( elem ); + return results; + } + } + + // Speed-up: Sizzle("TAG") + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Speed-up: Sizzle(".CLASS") + } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) { + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // QSA path + if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + nid = old = expando; + newContext = context; + newSelector = nodeType === 9 && selector; + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + groups = tokenize( selector ); + + if ( (old = context.getAttribute("id")) ) { + nid = old.replace( rescape, "\\$&" ); + } else { + context.setAttribute( "id", nid ); + } + nid = "[id='" + nid + "'] "; + + i = groups.length; + while ( i-- ) { + groups[i] = nid + toSelector( groups[i] ); + } + newContext = rsibling.test( selector ) && context.parentNode || context; + newSelector = groups.join(","); + } + + if ( newSelector ) { + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch(qsaError) { + } finally { + if ( !old ) { + context.removeAttribute("id"); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {Function(string, Object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key += " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created div and expects a boolean result + */ +function assert( fn ) { + var div = document.createElement("div"); + + try { + return !!fn( div ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( div.parentNode ) { + div.parentNode.removeChild( div ); + } + // release memory in IE + div = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = attrs.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + ( ~b.sourceIndex || MAX_NEGATIVE ) - + ( ~a.sourceIndex || MAX_NEGATIVE ); + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Detect xml + * @param {Element|Object} elem An element or a document + */ +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var doc = node ? node.ownerDocument || node : preferredDoc, + parent = doc.defaultView; + + // If no document and documentElement is available, return + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Set our document + document = doc; + docElem = doc.documentElement; + + // Support tests + documentIsHTML = !isXML( doc ); + + // Support: IE>8 + // If iframe document is assigned to "document" variable and if iframe has been reloaded, + // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936 + // IE6-8 do not support the defaultView property so parent will be undefined + if ( parent && parent.attachEvent && parent !== parent.top ) { + parent.attachEvent( "onbeforeunload", function() { + setDocument(); + }); + } + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans) + support.attributes = assert(function( div ) { + div.className = "i"; + return !div.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( div ) { + div.appendChild( doc.createComment("") ); + return !div.getElementsByTagName("*").length; + }); + + // Check if getElementsByClassName can be trusted + support.getElementsByClassName = assert(function( div ) { + div.innerHTML = "

"; + + // Support: Safari<4 + // Catch class over-caching + div.firstChild.className = "i"; + // Support: Opera<10 + // Catch gEBCN failure to find non-leading classes + return div.getElementsByClassName("i").length === 2; + }); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( div ) { + docElem.appendChild( div ).id = expando; + return !doc.getElementsByName || !doc.getElementsByName( expando ).length; + }); + + // ID find and filter + if ( support.getById ) { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== strundefined && documentIsHTML ) { + var m = context.getElementById( id ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + } else { + // Support: IE6/7 + // getElementById is not reliable as a find shortcut + delete Expr.find["ID"]; + + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== strundefined ) { + return context.getElementsByTagName( tag ); + } + } : + function( tag, context ) { + var elem, + tmp = [], + i = 0, + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See http://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( div ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // http://bugs.jquery.com/ticket/12359 + div.innerHTML = ""; + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !div.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + }); + + assert(function( div ) { + + // Support: Opera 10-12/IE8 + // ^= $= *= and empty values + // Should not select anything + // Support: Windows 8 Native Apps + // The type attribute is restricted during .innerHTML assignment + var input = doc.createElement("input"); + input.setAttribute( "type", "hidden" ); + div.appendChild( input ).setAttribute( "t", "" ); + + if ( div.querySelectorAll("[t^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":enabled").length ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + div.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( div ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( div, "div" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( div, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + + // Element contains another + // Purposefully does not implement inclusive descendent + // As in, an element does not contain itself + contains = rnative.test( docElem.contains ) || docElem.compareDocumentPosition ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = docElem.compareDocumentPosition ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b ); + + if ( compare ) { + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === doc || contains(preferredDoc, a) ) { + return -1; + } + if ( b === doc || contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } + + // Not directly comparable, sort on existence of method + return a.compareDocumentPosition ? -1 : 1; + } : + function( a, b ) { + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + + // Parentless nodes are either documents or disconnected + } else if ( !aup || !bup ) { + return a === doc ? -1 : + b === doc ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return doc; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + if ( support.matchesSelector && documentIsHTML && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch(e) {} + } + + return Sizzle( expr, document, null, [elem] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val === undefined ? + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null : + val; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + for ( ; (node = elem[i]); i++ ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (see #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[5] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] && match[4] !== undefined ) { + match[2] = match[4]; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, outerCache, node, diff, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || (parent[ expando ] = {}); + cache = outerCache[ type ] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = cache[0] === dirruns && cache[2]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + // Use previously-cached element index if available + } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { + diff = cache[1]; + + // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) + } else { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { + // Cache the index of each encountered element + if ( useCache ) { + (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf.call( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)), + // not comment, processing instructions, or others + // Thanks to Diego Perini for the nodeName shortcut + // Greater than "@" means alpha characters (specifically not starting with "#" or "?") + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === elem.type ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +function tokenize( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( tokens = [] ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +} + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + checkNonElements = base && dir === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var data, cache, outerCache, + dirkey = dirruns + " " + doneName; + + // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) { + if ( (data = cache[1]) === true || data === cachedruns ) { + return data === true; + } + } else { + cache = outerCache[ dir ] = [ dirkey ]; + cache[1] = matcher( elem, context, xml ) || cachedruns; + if ( cache[1] === true ) { + return true; + } + } + } + } + } + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + // A counter to specify which element is currently being matched + var matcherCachedRuns = 0, + bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, expandContext ) { + var elem, j, matcher, + setMatched = [], + matchedCount = 0, + i = "0", + unmatched = seed && [], + outermost = expandContext != null, + contextBackup = outermostContext, + // We must always have either seed elements or context + elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1); + + if ( outermost ) { + outermostContext = context !== document && context; + cachedruns = matcherCachedRuns; + } + + // Add elements passing elementMatchers directly to results + // Keep `i` a string if there are no elements so `matchedCount` will be "00" below + for ( ; (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + cachedruns = ++matcherCachedRuns; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // Apply set filters to unmatched elements + matchedCount += i; + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !group ) { + group = tokenize( selector ); + } + i = group.length; + while ( i-- ) { + cached = matcherFromTokens( group[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + } + return cached; +}; + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function select( selector, context, results, seed ) { + var i, tokens, token, type, find, + match = tokenize( selector ); + + if ( !seed ) { + // Try to minimize operations if there is only one group + if ( match.length === 1 ) { + + // Take a shortcut and set the context if the root selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + support.getById && context.nodeType === 9 && documentIsHTML && + Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + } + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && context.parentNode || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + } + + // Compile and execute a filtering function + // Provide `match` to avoid retokenization if we modified the selector above + compile( selector, match )( + seed, + context, + !documentIsHTML, + results, + rsibling.test( selector ) + ); + return results; +} + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Support: Chrome<14 +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert(function( div1 ) { + // Should return 1, but returns 4 (following) + return div1.compareDocumentPosition( document.createElement("div") ) & 1; +}); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert(function( div ) { + div.innerHTML = ""; + return div.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert(function( div ) { + div.innerHTML = ""; + div.firstChild.setAttribute( "value", "" ); + return div.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert(function( div ) { + return div.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + elem[ name ] === true ? name.toLowerCase() : null; + } + }); +} + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.pseudos; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})( window ); +// String to Object options format cache +var optionsCache = {}; + +// Convert String-formatted options into Object-formatted ones and store in cache +function createOptions( options ) { + var object = optionsCache[ options ] = {}; + jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) { + object[ flag ] = true; + }); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // First callback to fire (used internally by add and fireWith) + firingStart, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], + // Fire callbacks + fire = function( data ) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add + break; + } + } + firing = false; + if ( list ) { + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); + } + } else if ( memory ) { + list = []; + } else { + self.disable(); + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + // First, we save the current length + var start = list.length; + (function add( args ) { + jQuery.each( args, function( _, arg ) { + var type = jQuery.type( arg ); + if ( type === "function" ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && type !== "string" ) { + // Inspect recursively + add( arg ); + } + }); + })( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + jQuery.each( arguments, function( _, arg ) { + var index; + while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; + } + if ( index <= firingIndex ) { + firingIndex--; + } + } + } + }); + } + return this; + }, + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); + }, + // Remove all callbacks from the list + empty: function() { + list = []; + firingLength = 0; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( list && ( !fired || stack ) ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( firing ) { + stack.push( args ); + } else { + fire( args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; +jQuery.extend({ + + Deferred: function( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], + [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], + [ "notify", "progress", jQuery.Callbacks("memory") ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return jQuery.Deferred(function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + var action = tuple[ 0 ], + fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[1] ](function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); + } + }); + }); + fns = null; + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[1] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] + deferred[ tuple[0] ] = function() { + deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); + return this; + }; + deferred[ tuple[0] + "With" ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = core_slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value; + if( values === progressValues ) { + deferred.notifyWith( contexts, values ); + } else if ( !( --remaining ) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); + } else { + --remaining; + } + } + } + + // if we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } +}); +jQuery.support = (function( support ) { + + var all, a, input, select, fragment, opt, eventName, isSupported, i, + div = document.createElement("div"); + + // Setup + div.setAttribute( "className", "t" ); + div.innerHTML = "
a"; + + // Finish early in limited (non-browser) environments + all = div.getElementsByTagName("*") || []; + a = div.getElementsByTagName("a")[ 0 ]; + if ( !a || !a.style || !all.length ) { + return support; + } + + // First batch of tests + select = document.createElement("select"); + opt = select.appendChild( document.createElement("option") ); + input = div.getElementsByTagName("input")[ 0 ]; + + a.style.cssText = "top:1px;float:left;opacity:.5"; + + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + support.getSetAttribute = div.className !== "t"; + + // IE strips leading whitespace when .innerHTML is used + support.leadingWhitespace = div.firstChild.nodeType === 3; + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + support.tbody = !div.getElementsByTagName("tbody").length; + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + support.htmlSerialize = !!div.getElementsByTagName("link").length; + + // Get the style information from getAttribute + // (IE uses .cssText instead) + support.style = /top/.test( a.getAttribute("style") ); + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + support.hrefNormalized = a.getAttribute("href") === "/a"; + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + support.opacity = /^0.5/.test( a.style.opacity ); + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + support.cssFloat = !!a.style.cssFloat; + + // Check the default checkbox/radio value ("" on WebKit; "on" elsewhere) + support.checkOn = !!input.value; + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + support.optSelected = opt.selected; + + // Tests for enctype support on a form (#6743) + support.enctype = !!document.createElement("form").enctype; + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + support.html5Clone = document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>"; + + // Will be defined later + support.inlineBlockNeedsLayout = false; + support.shrinkWrapBlocks = false; + support.pixelPosition = false; + support.deleteExpando = true; + support.noCloneEvent = true; + support.reliableMarginRight = true; + support.boxSizingReliable = true; + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode( true ).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Support: IE<9 + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + + // Check if we can trust getAttribute("value") + input = document.createElement("input"); + input.setAttribute( "value", "" ); + support.input = input.getAttribute( "value" ) === ""; + + // Check if an input maintains its value after becoming a radio + input.value = "t"; + input.setAttribute( "type", "radio" ); + support.radioValue = input.value === "t"; + + // #11217 - WebKit loses check when the name is after the checked attribute + input.setAttribute( "checked", "t" ); + input.setAttribute( "name", "t" ); + + fragment = document.createDocumentFragment(); + fragment.appendChild( input ); + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE<9 + // Opera does not clone events (and typeof div.attachEvent === undefined). + // IE9-10 clones events bound via attachEvent, but they don't trigger with .click() + if ( div.attachEvent ) { + div.attachEvent( "onclick", function() { + support.noCloneEvent = false; + }); + + div.cloneNode( true ).click(); + } + + // Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event) + // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP) + for ( i in { submit: true, change: true, focusin: true }) { + div.setAttribute( eventName = "on" + i, "t" ); + + support[ i + "Bubbles" ] = eventName in window || div.attributes[ eventName ].expando === false; + } + + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + // Support: IE<9 + // Iteration over object's inherited properties before its own. + for ( i in jQuery( support ) ) { + break; + } + support.ownLast = i !== "0"; + + // Run tests that need a body at doc ready + jQuery(function() { + var container, marginDiv, tds, + divReset = "padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;", + body = document.getElementsByTagName("body")[0]; + + if ( !body ) { + // Return for frameset docs that don't have a body + return; + } + + container = document.createElement("div"); + container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px"; + + body.appendChild( container ).appendChild( div ); + + // Support: IE8 + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + div.innerHTML = "
t
"; + tds = div.getElementsByTagName("td"); + tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none"; + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Support: IE8 + // Check if empty table cells still have offsetWidth/Height + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + + // Check box-sizing and margin behavior. + div.innerHTML = ""; + div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;"; + + // Workaround failing boxSizing test due to offsetWidth returning wrong value + // with some non-1 values of body zoom, ticket #13543 + jQuery.swap( body, body.style.zoom != null ? { zoom: 1 } : {}, function() { + support.boxSizing = div.offsetWidth === 4; + }); + + // Use window.getComputedStyle because jsdom on node.js will break without it. + if ( window.getComputedStyle ) { + support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%"; + support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px"; + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. (#3333) + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + marginDiv = div.appendChild( document.createElement("div") ); + marginDiv.style.cssText = div.style.cssText = divReset; + marginDiv.style.marginRight = marginDiv.style.width = "0"; + div.style.width = "1px"; + + support.reliableMarginRight = + !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight ); + } + + if ( typeof div.style.zoom !== core_strundefined ) { + // Support: IE<8 + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + div.innerHTML = ""; + div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1"; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); + + // Support: IE6 + // Check if elements with layout shrink-wrap their children + div.style.display = "block"; + div.innerHTML = "
"; + div.firstChild.style.width = "5px"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); + + if ( support.inlineBlockNeedsLayout ) { + // Prevent IE 6 from affecting layout for positioned elements #11048 + // Prevent IE from shrinking the body in IE 7 mode #12869 + // Support: IE<8 + body.style.zoom = 1; + } + } + + body.removeChild( container ); + + // Null elements to avoid leaks in IE + container = div = tds = marginDiv = null; + }); + + // Null elements to avoid leaks in IE + all = select = fragment = opt = a = input = null; + + return support; +})({}); + +var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, + rmultiDash = /([A-Z])/g; + +function internalData( elem, name, data, pvt /* Internal Use Only */ ){ + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var ret, thisCache, + internalKey = jQuery.expando, + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === "string" ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + id = elem[ internalKey ] = core_deletedIds.pop() || jQuery.guid++; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + // Avoid exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + cache[ id ] = isNode ? {} : { toJSON: jQuery.noop }; + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( typeof name === "string" ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; +} + +function internalRemoveData( elem, name, pvt ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, i, + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split(" "); + } + } + } else { + // If "name" is an array of keys... + // When data is initially created, via ("key", "val") signature, + // keys will be converted to camelCase. + // Since there is no way to tell _how_ a key was added, remove + // both plain key and camelCase key. #12786 + // This will only penalize the array argument path. + name = name.concat( jQuery.map( name, jQuery.camelCase ) ); + } + + i = name.length; + while ( i-- ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject( cache[ id ] ) ) { + return; + } + } + + // Destroy the cache + if ( isNode ) { + jQuery.cleanData( [ elem ], true ); + + // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) + /* jshint eqeqeq: false */ + } else if ( jQuery.support.deleteExpando || cache != cache.window ) { + /* jshint eqeqeq: true */ + delete cache[ id ]; + + // When all else fails, null + } else { + cache[ id ] = null; + } +} + +jQuery.extend({ + cache: {}, + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "applet": true, + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data ) { + return internalData( elem, name, data ); + }, + + removeData: function( elem, name ) { + return internalRemoveData( elem, name ); + }, + + // For internal use only. + _data: function( elem, name, data ) { + return internalData( elem, name, data, true ); + }, + + _removeData: function( elem, name ) { + return internalRemoveData( elem, name, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + // Do not set data on non-element because it will not be cleared (#8335). + if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) { + return false; + } + + var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ]; + + // nodes accept data unless otherwise specified; rejection can be conditional + return !noData || noData !== true && elem.getAttribute("classid") === noData; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var attrs, name, + data = null, + i = 0, + elem = this[0]; + + // Special expections of .data basically thwart jQuery.access, + // so implement the relevant behavior ourselves + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = jQuery.data( elem ); + + if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { + attrs = elem.attributes; + for ( ; i < attrs.length; i++ ) { + name = attrs[i].name; + + if ( name.indexOf("data-") === 0 ) { + name = jQuery.camelCase( name.slice(5) ); + + dataAttr( elem, name, data[ name ] ); + } + } + jQuery._data( elem, "parsedAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + return arguments.length > 1 ? + + // Sets one value + this.each(function() { + jQuery.data( this, key, value ); + }) : + + // Gets one value + // Try to fetch any internally stored data first + elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null; + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + var name; + for ( name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} +jQuery.extend({ + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray(data) ) { + queue = jQuery._data( elem, type, jQuery.makeArray(data) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // not intended for public consumption - generates a queueHooks object, or returns the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return jQuery._data( elem, key ) || jQuery._data( elem, key, { + empty: jQuery.Callbacks("once memory").add(function() { + jQuery._removeData( elem, type + "queue" ); + jQuery._removeData( elem, key ); + }) + }); + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + // ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = setTimeout( next, time ); + hooks.stop = function() { + clearTimeout( timeout ); + }; + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while( i-- ) { + tmp = jQuery._data( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +}); +var nodeHook, boolHook, + rclass = /[\t\r\n\f]/g, + rreturn = /\r/g, + rfocusable = /^(?:input|select|textarea|button|object)$/i, + rclickable = /^(?:a|area)$/i, + ruseDefault = /^(?:checked|selected)$/i, + getSetAttribute = jQuery.support.getSetAttribute, + getSetInput = jQuery.support.input; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each(function() { + jQuery.removeAttr( this, name ); + }); + }, + + prop: function( name, value ) { + return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + name = jQuery.propFix[ name ] || name; + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} + }); + }, + + addClass: function( value ) { + var classes, elem, cur, clazz, j, + i = 0, + len = this.length, + proceed = typeof value === "string" && value; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).addClass( value.call( this, j, this.className ) ); + }); + } + + if ( proceed ) { + // The disjunction here is for better compressibility (see removeClass) + classes = ( value || "" ).match( core_rnotwhite ) || []; + + for ( ; i < len; i++ ) { + elem = this[ i ]; + cur = elem.nodeType === 1 && ( elem.className ? + ( " " + elem.className + " " ).replace( rclass, " " ) : + " " + ); + + if ( cur ) { + j = 0; + while ( (clazz = classes[j++]) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + elem.className = jQuery.trim( cur ); + + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, clazz, j, + i = 0, + len = this.length, + proceed = arguments.length === 0 || typeof value === "string" && value; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).removeClass( value.call( this, j, this.className ) ); + }); + } + if ( proceed ) { + classes = ( value || "" ).match( core_rnotwhite ) || []; + + for ( ; i < len; i++ ) { + elem = this[ i ]; + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( elem.className ? + ( " " + elem.className + " " ).replace( rclass, " " ) : + "" + ); + + if ( cur ) { + j = 0; + while ( (clazz = classes[j++]) ) { + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) >= 0 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + elem.className = value ? jQuery.trim( cur ) : ""; + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value; + + if ( typeof stateVal === "boolean" && type === "string" ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( jQuery.isFunction( value ) ) { + return this.each(function( i ) { + jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + classNames = value.match( core_rnotwhite ) || []; + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( type === core_strundefined || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery._data( this, "__className__", this.className ); + } + + // If the element has a class name or if we're passed "false", + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " ", + i = 0, + l = this.length; + for ( ; i < l; i++ ) { + if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + var ret, hooks, isFunction, + elem = this[0]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { + return ret; + } + + ret = elem.value; + + return typeof ret === "string" ? + // handle most common string cases + ret.replace(rreturn, "") : + // handle cases where value is null/undef or number + ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( value ); + + return this.each(function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map(val, function ( value ) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + valHooks: { + option: { + get: function( elem ) { + // Use proper attribute retrieval(#6932, #12072) + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + elem.text; + } + }, + select: { + get: function( elem ) { + var value, option, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one" || index < 0, + values = one ? null : [], + max = one ? index + 1 : options.length, + i = index < 0 ? + max : + one ? index : 0; + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // oldIE doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + // Don't return options that are disabled or in a disabled optgroup + ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) && + ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + if ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) { + optionSet = true; + } + } + + // force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + attr: function( elem, name, value ) { + var hooks, ret, + nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === core_strundefined ) { + return jQuery.prop( elem, name, value ); + } + + // All attributes are lowercase + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + name = name.toLowerCase(); + hooks = jQuery.attrHooks[ name ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook ); + } + + if ( value !== undefined ) { + + if ( value === null ) { + jQuery.removeAttr( elem, name ); + + } else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + elem.setAttribute( name, value + "" ); + return value; + } + + } else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? + undefined : + ret; + } + }, + + removeAttr: function( elem, value ) { + var name, propName, + i = 0, + attrNames = value && value.match( core_rnotwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( (name = attrNames[i++]) ) { + propName = jQuery.propFix[ name ] || name; + + // Boolean attributes get special treatment (#10870) + if ( jQuery.expr.match.bool.test( name ) ) { + // Set corresponding property to false + if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) { + elem[ propName ] = false; + // Support: IE<9 + // Also clear defaultChecked/defaultSelected (if appropriate) + } else { + elem[ jQuery.camelCase( "default-" + name ) ] = + elem[ propName ] = false; + } + + // See #9699 for explanation of this approach (setting first, then removal) + } else { + jQuery.attr( elem, name, "" ); + } + + elem.removeAttribute( getSetAttribute ? name : propName ); + } + } + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to default in case type is set after value during creation + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + }, + + prop: function( elem, name, value ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + if ( notxml ) { + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + return hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ? + ret : + ( elem[ name ] = value ); + + } else { + return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ? + ret : + elem[ name ]; + } + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + return tabindex ? + parseInt( tabindex, 10 ) : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + -1; + } + } + } +}); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) { + // IE<8 needs the *property* name + elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name ); + + // Use defaultChecked and defaultSelected for oldIE + } else { + elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true; + } + + return name; + } +}; +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { + var getter = jQuery.expr.attrHandle[ name ] || jQuery.find.attr; + + jQuery.expr.attrHandle[ name ] = getSetInput && getSetAttribute || !ruseDefault.test( name ) ? + function( elem, name, isXML ) { + var fn = jQuery.expr.attrHandle[ name ], + ret = isXML ? + undefined : + /* jshint eqeqeq: false */ + (jQuery.expr.attrHandle[ name ] = undefined) != + getter( elem, name, isXML ) ? + + name.toLowerCase() : + null; + jQuery.expr.attrHandle[ name ] = fn; + return ret; + } : + function( elem, name, isXML ) { + return isXML ? + undefined : + elem[ jQuery.camelCase( "default-" + name ) ] ? + name.toLowerCase() : + null; + }; +}); + +// fix oldIE attroperties +if ( !getSetInput || !getSetAttribute ) { + jQuery.attrHooks.value = { + set: function( elem, value, name ) { + if ( jQuery.nodeName( elem, "input" ) ) { + // Does not return so that setAttribute is also used + elem.defaultValue = value; + } else { + // Use nodeHook if defined (#1954); otherwise setAttribute is fine + return nodeHook && nodeHook.set( elem, value, name ); + } + } + }; +} + +// IE6/7 do not support getting/setting some attributes with get/setAttribute +if ( !getSetAttribute ) { + + // Use this for any attribute in IE6/7 + // This fixes almost every IE6/7 issue + nodeHook = { + set: function( elem, value, name ) { + // Set the existing or create a new attribute node + var ret = elem.getAttributeNode( name ); + if ( !ret ) { + elem.setAttributeNode( + (ret = elem.ownerDocument.createAttribute( name )) + ); + } + + ret.value = value += ""; + + // Break association with cloned elements by also using setAttribute (#9646) + return name === "value" || value === elem.getAttribute( name ) ? + value : + undefined; + } + }; + jQuery.expr.attrHandle.id = jQuery.expr.attrHandle.name = jQuery.expr.attrHandle.coords = + // Some attributes are constructed with empty-string values when not defined + function( elem, name, isXML ) { + var ret; + return isXML ? + undefined : + (ret = elem.getAttributeNode( name )) && ret.value !== "" ? + ret.value : + null; + }; + jQuery.valHooks.button = { + get: function( elem, name ) { + var ret = elem.getAttributeNode( name ); + return ret && ret.specified ? + ret.value : + undefined; + }, + set: nodeHook.set + }; + + // Set contenteditable to false on removals(#10429) + // Setting to empty string throws an error as an invalid value + jQuery.attrHooks.contenteditable = { + set: function( elem, value, name ) { + nodeHook.set( elem, value === "" ? false : value, name ); + } + }; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each([ "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = { + set: function( elem, value ) { + if ( value === "" ) { + elem.setAttribute( name, "auto" ); + return value; + } + } + }; + }); +} + + +// Some attributes require a special call on IE +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !jQuery.support.hrefNormalized ) { + // href/src property should get the full normalized URL (#10299/#12915) + jQuery.each([ "href", "src" ], function( i, name ) { + jQuery.propHooks[ name ] = { + get: function( elem ) { + return elem.getAttribute( name, 4 ); + } + }; + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + // Return undefined in the case of empty string + // Note: IE uppercases css property names, but if we were to .toLowerCase() + // .cssText, that would destroy case senstitivity in URL's, like in "background" + return elem.style.cssText || undefined; + }, + set: function( elem, value ) { + return ( elem.style.cssText = value + "" ); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + return null; + } + }; +} + +jQuery.each([ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +}); + +// IE6/7 call enctype encoding +if ( !jQuery.support.enctype ) { + jQuery.propFix.enctype = "encoding"; +} + +// Radios and checkboxes getter/setter +jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); + } + } + }; + if ( !jQuery.support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + // Support: Webkit + // "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + }; + } +}); +var rformElems = /^(?:input|select|textarea)$/i, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + var tmp, events, t, handleObjIn, + special, eventHandle, handleObj, + handlers, type, namespaces, origType, + elemData = jQuery._data( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !(events = elemData.events) ) { + events = elemData.events = {}; + } + if ( !(eventHandle = elemData.handle) ) { + eventHandle = elemData.handle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( core_rnotwhite ) || [""]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !(handlers = events[ type ]) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + var j, handleObj, tmp, + origCount, t, events, + special, handlers, type, + namespaces, origType, + elemData = jQuery.hasData( elem ) && jQuery._data( elem ); + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( core_rnotwhite ) || [""]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + delete elemData.handle; + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery._removeData( elem, "events" ); + } + }, + + trigger: function( event, data, elem, onlyHandlers ) { + var handle, ontype, cur, + bubbleType, special, tmp, i, + eventPath = [ elem || document ], + type = core_hasOwn.call( event, "type" ) ? event.type : event, + namespaces = core_hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf(":") < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join("."); + event.namespace_re = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === (elem.ownerDocument || document) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) { + event.preventDefault(); + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) && + jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + try { + elem[ type ](); + } catch ( e ) { + // IE<9 dies on focus/blur to hidden element (#1486,#12518) + // only reproducible on winXP IE8 native, not IE9 in IE8 mode + } + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event ); + + var i, ret, handleObj, matched, j, + handlerQueue = [], + args = core_slice.call( arguments ), + handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( (event.result = ret) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var sel, handleObj, matches, i, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + // Black-hole SVG instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { + + /* jshint eqeqeq: false */ + for ( ; cur != this; cur = cur.parentNode || this ) { + /* jshint eqeqeq: true */ + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) { + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matches[ sel ] === undefined ) { + matches[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matches[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, handlers: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( delegateCount < handlers.length ) { + handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); + } + + return handlerQueue; + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, copy, + type = event.type, + originalEvent = event, + fixHook = this.fixHooks[ type ]; + + if ( !fixHook ) { + this.fixHooks[ type ] = fixHook = + rmouseEvent.test( type ) ? this.mouseHooks : + rkeyEvent.test( type ) ? this.keyHooks : + {}; + } + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = new jQuery.Event( originalEvent ); + + i = copy.length; + while ( i-- ) { + prop = copy[ i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Support: IE<9 + // Fix target property (#1925) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Support: Chrome 23+, Safari? + // Target should not be a text node (#504, #13143) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Support: IE<9 + // For mouse/key events, metaKey==false if it's undefined (#3368, #11328) + event.metaKey = !!event.metaKey; + + return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var body, eventDoc, doc, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + special: { + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + focus: { + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== safeActiveElement() && this.focus ) { + try { + this.focus(); + return false; + } catch ( e ) { + // Support: IE<9 + // If we error on focus to hidden element (#1486, #12518), + // let .trigger() run the handlers + } + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === safeActiveElement() && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + click: { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) { + this.click(); + return false; + } + }, + + // For cross-browser consistency, don't fire native .click() on links + _default: function( event ) { + return jQuery.nodeName( event.target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Even when returnValue equals to undefined Firefox will still show alert + if ( event.result !== undefined ) { + event.originalEvent.returnValue = event.result; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + var name = "on" + type; + + if ( elem.detachEvent ) { + + // #8545, #7054, preventing memory leaks for custom events in IE6-8 + // detachEvent needed property on element, by name of that event, to properly expose it to GC + if ( typeof elem[ name ] === core_strundefined ) { + elem[ name ] = null; + } + + elem.detachEvent( name, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + if ( !e ) { + return; + } + + // If preventDefault exists, run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // Support: IE + // Otherwise set the returnValue property of the original event to false + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + if ( !e ) { + return; + } + // If stopPropagation exists, run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + + // Support: IE + // Set the cancelBubble property of the original event to true + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + } +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !jQuery._data( form, "submitBubbles" ) ) { + jQuery.event.add( form, "submit._submit", function( event ) { + event._submit_bubble = true; + }); + jQuery._data( form, "submitBubbles", true ); + } + }); + // return undefined since we don't need an event listener + }, + + postDispatch: function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( event._submit_bubble ) { + delete event._submit_bubble; + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + } + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !jQuery.support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + } + // Allow triggered, simulated change events (#11500) + jQuery.event.simulate( "change", this, event, true ); + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + jQuery._data( elem, "changeBubbles", true ); + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return !rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !jQuery.support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0, + handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var type, origFn; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + var elem = this[0]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +}); +var isSimple = /^.[^:#\[\.,]*$/, + rparentsprev = /^(?:parents|prev(?:Until|All))/, + rneedsContext = jQuery.expr.match.needsContext, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend({ + find: function( selector ) { + var i, + ret = [], + self = this, + len = self.length; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }) ); + } + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + // Needed because $( selector, context ) becomes $( context ).find( selector ) + ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); + ret.selector = this.selector ? this.selector + " " + selector : selector; + return ret; + }, + + has: function( target ) { + var i, + targets = jQuery( target, this ), + len = targets.length; + + return this.filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector || [], true) ); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector || [], false) ); + }, + + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + ret = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { + // Always skip document fragments + if ( cur.nodeType < 11 && (pos ? + pos.index(cur) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector(cur, selectors)) ) { + + cur = ret.push( cur ); + break; + } + } + } + + return this.pushStack( ret.length > 1 ? jQuery.unique( ret ) : ret ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( jQuery.unique(all) ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } +}); + +function sibling( cur, dir ) { + do { + cur = cur[ dir ]; + } while ( cur && cur.nodeType !== 1 ); + + return cur; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + if ( this.length > 1 ) { + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + ret = jQuery.unique( ret ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + } + + return this.pushStack( ret ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 && elem.nodeType === 1 ? + jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : + jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + })); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + /* jshint -W018 */ + return !!qualifier.call( elem, i, elem ) !== not; + }); + + } + + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + }); + + } + + if ( typeof qualifier === "string" ) { + if ( isSimple.test( qualifier ) ) { + return jQuery.filter( qualifier, elements, not ); + } + + qualifier = jQuery.filter( qualifier, elements ); + } + + return jQuery.grep( elements, function( elem ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) !== not; + }); +} +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, + rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"), + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rtbody = /\s*$/g, + + // We have to close these tags to support XHTML (#13200) + wrapMap = { + option: [ 1, "" ], + legend: [ 1, "
", "
" ], + area: [ 1, "", "" ], + param: [ 1, "", "" ], + thead: [ 1, "", "
" ], + tr: [ 2, "", "
" ], + col: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + // IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, + // unless wrapped in a div with non-breaking characters in front of it. + _default: jQuery.support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X
", "
" ] + }, + safeFragment = createSafeFragment( document ), + fragmentDiv = safeFragment.appendChild( document.createElement("div") ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +jQuery.fn.extend({ + text: function( value ) { + return jQuery.access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) ); + }, null, value, arguments.length ); + }, + + append: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + }); + }, + + before: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + }); + }, + + after: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + }); + }, + + // keepData is for internal use only--do not document + remove: function( selector, keepData ) { + var elem, + elems = selector ? jQuery.filter( selector, this ) : this, + i = 0; + + for ( ; (elem = elems[i]) != null; i++ ) { + + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem ) ); + } + + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } + elem.parentNode.removeChild( elem ); + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + } + + // Remove any remaining nodes + while ( elem.firstChild ) { + elem.removeChild( elem.firstChild ); + } + + // If this is a select, ensure that it displays empty (#12336) + // Support: IE<9 + if ( elem.options && jQuery.nodeName( elem, "select" ) ) { + elem.options.length = 0; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function () { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + return jQuery.access( this, function( value ) { + var elem = this[0] || {}, + i = 0, + l = this.length; + + if ( value === undefined ) { + return elem.nodeType === 1 ? + elem.innerHTML.replace( rinlinejQuery, "" ) : + undefined; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) && + ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && + !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1>" ); + + try { + for (; i < l; i++ ) { + // Remove element nodes and prevent memory leaks + elem = this[i] || {}; + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch(e) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var + // Snapshot the DOM in case .domManip sweeps something relevant into its fragment + args = jQuery.map( this, function( elem ) { + return [ elem.nextSibling, elem.parentNode ]; + }), + i = 0; + + // Make the changes, replacing each context element with the new content + this.domManip( arguments, function( elem ) { + var next = args[ i++ ], + parent = args[ i++ ]; + + if ( parent ) { + // Don't use the snapshot next if it has moved (#13810) + if ( next && next.parentNode !== parent ) { + next = this.nextSibling; + } + jQuery( this ).remove(); + parent.insertBefore( elem, next ); + } + // Allow new content to include elements from the context set + }, true ); + + // Force removal if there was no new content (e.g., from empty arguments) + return i ? this : this.remove(); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, callback, allowIntersection ) { + + // Flatten any nested arrays + args = core_concat.apply( [], args ); + + var first, node, hasScripts, + scripts, doc, fragment, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[0], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) { + return this.each(function( index ) { + var self = set.eq( index ); + if ( isFunction ) { + args[0] = value.call( this, index, self.html() ); + } + self.domManip( args, callback, allowIntersection ); + }); + } + + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, !allowIntersection && this ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( this[i], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + // Hope ajax is available... + jQuery._evalUrl( node.src ); + } else { + jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) ); + } + } + } + } + + // Fix #11809: Avoid leaking memory + fragment = first = null; + } + } + + return this; + } +}); + +// Support: IE<8 +// Manipulating tables requires a tbody +function manipulationTarget( elem, content ) { + return jQuery.nodeName( elem, "table" ) && + jQuery.nodeName( content.nodeType === 1 ? content : content.firstChild, "tr" ) ? + + elem.getElementsByTagName("tbody")[0] || + elem.appendChild( elem.ownerDocument.createElement("tbody") ) : + elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = (jQuery.find.attr( elem, "type" ) !== null) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + if ( match ) { + elem.type = match[1]; + } else { + elem.removeAttribute("type"); + } + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var elem, + i = 0; + for ( ; (elem = elems[i]) != null; i++ ) { + jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) ); + } +} + +function cloneCopyEvent( src, dest ) { + + if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { + return; + } + + var type, i, l, + oldData = jQuery._data( src ), + curData = jQuery._data( dest, oldData ), + events = oldData.events; + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + + // make the cloned public data object a copy from the original + if ( curData.data ) { + curData.data = jQuery.extend( {}, curData.data ); + } +} + +function fixCloneNodeIssues( src, dest ) { + var nodeName, e, data; + + // We do not need to do anything for non-Elements + if ( dest.nodeType !== 1 ) { + return; + } + + nodeName = dest.nodeName.toLowerCase(); + + // IE6-8 copies events bound via attachEvent when using cloneNode. + if ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) { + data = jQuery._data( dest ); + + for ( e in data.events ) { + jQuery.removeEvent( dest, e, data.handle ); + } + + // Event data gets referenced instead of copied if the expando gets copied too + dest.removeAttribute( jQuery.expando ); + } + + // IE blanks contents when cloning scripts, and tries to evaluate newly-set text + if ( nodeName === "script" && dest.text !== src.text ) { + disableScript( dest ).text = src.text; + restoreScript( dest ); + + // IE6-10 improperly clones children of object elements using classid. + // IE10 throws NoModificationAllowedError if parent is null, #12132. + } else if ( nodeName === "object" ) { + if ( dest.parentNode ) { + dest.outerHTML = src.outerHTML; + } + + // This path appears unavoidable for IE9. When cloning an object + // element in IE9, the outerHTML strategy above is not sufficient. + // If the src has innerHTML and the destination does not, + // copy the src.innerHTML into the dest.innerHTML. #10324 + if ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) { + dest.innerHTML = src.innerHTML; + } + + } else if ( nodeName === "input" && manipulation_rcheckableType.test( src.type ) ) { + // IE6-8 fails to persist the checked state of a cloned checkbox + // or radio button. Worse, IE6-7 fail to give the cloned element + // a checked appearance if the defaultChecked value isn't also set + + dest.defaultChecked = dest.checked = src.checked; + + // IE6-7 get confused and end up setting the value of a cloned + // checkbox/radio button to an empty string instead of "on" + if ( dest.value !== src.value ) { + dest.value = src.value; + } + + // IE6-8 fails to return the selected option to the default selected + // state when cloning options + } else if ( nodeName === "option" ) { + dest.defaultSelected = dest.selected = src.defaultSelected; + + // IE6-8 fails to set the defaultValue to the correct value when + // cloning other types of input fields + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + i = 0, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone(true); + jQuery( insert[i] )[ original ]( elems ); + + // Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get() + core_push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +}); + +function getAll( context, tag ) { + var elems, elem, + i = 0, + found = typeof context.getElementsByTagName !== core_strundefined ? context.getElementsByTagName( tag || "*" ) : + typeof context.querySelectorAll !== core_strundefined ? context.querySelectorAll( tag || "*" ) : + undefined; + + if ( !found ) { + for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) { + if ( !tag || jQuery.nodeName( elem, tag ) ) { + found.push( elem ); + } else { + jQuery.merge( found, getAll( elem, tag ) ); + } + } + } + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], found ) : + found; +} + +// Used in buildFragment, fixes the defaultChecked property +function fixDefaultChecked( elem ) { + if ( manipulation_rcheckableType.test( elem.type ) ) { + elem.defaultChecked = elem.checked; + } +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var destElements, node, clone, i, srcElements, + inPage = jQuery.contains( elem.ownerDocument, elem ); + + if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { + clone = elem.cloneNode( true ); + + // IE<=8 does not properly clone detached, unknown element nodes + } else { + fragmentDiv.innerHTML = elem.outerHTML; + fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); + } + + if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && + (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { + + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + // Fix all IE cloning issues + for ( i = 0; (node = srcElements[i]) != null; ++i ) { + // Ensure that the destination node is not null; Fixes #9587 + if ( destElements[i] ) { + fixCloneNodeIssues( node, destElements[i] ); + } + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0; (node = srcElements[i]) != null; i++ ) { + cloneCopyEvent( node, destElements[i] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + destElements = srcElements = node = null; + + // Return the cloned set + return clone; + }, + + buildFragment: function( elems, context, scripts, selection ) { + var j, elem, contains, + tmp, tag, tbody, wrap, + l = elems.length, + + // Ensure a safe fragment + safe = createSafeFragment( context ), + + nodes = [], + i = 0; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || safe.appendChild( context.createElement("div") ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + + tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[2]; + + // Descend through wrappers to the right content + j = wrap[0]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Manually add leading whitespace removed by IE + if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) ); + } + + // Remove IE's autoinserted from table fragments + if ( !jQuery.support.tbody ) { + + // String was a , *may* have spurious + elem = tag === "table" && !rtbody.test( elem ) ? + tmp.firstChild : + + // String was a bare or + wrap[1] === "
" && !rtbody.test( elem ) ? + tmp : + 0; + + j = elem && elem.childNodes.length; + while ( j-- ) { + if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) { + elem.removeChild( tbody ); + } + } + } + + jQuery.merge( nodes, tmp.childNodes ); + + // Fix #12392 for WebKit and IE > 9 + tmp.textContent = ""; + + // Fix #12392 for oldIE + while ( tmp.firstChild ) { + tmp.removeChild( tmp.firstChild ); + } + + // Remember the top-level container for proper cleanup + tmp = safe.lastChild; + } + } + } + + // Fix #11356: Clear elements from fragment + if ( tmp ) { + safe.removeChild( tmp ); + } + + // Reset defaultChecked for any radios and checkboxes + // about to be appended to the DOM in IE 6/7 (#8060) + if ( !jQuery.support.appendChecked ) { + jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked ); + } + + i = 0; + while ( (elem = nodes[ i++ ]) ) { + + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( safe.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( (elem = tmp[ j++ ]) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + tmp = null; + + return safe; + }, + + cleanData: function( elems, /* internal */ acceptData ) { + var elem, type, id, data, + i = 0, + internalKey = jQuery.expando, + cache = jQuery.cache, + deleteExpando = jQuery.support.deleteExpando, + special = jQuery.event.special; + + for ( ; (elem = elems[i]) != null; i++ ) { + + if ( acceptData || jQuery.acceptData( elem ) ) { + + id = elem[ internalKey ]; + data = id && cache[ id ]; + + if ( data ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Remove cache only if it was not already removed by jQuery.event.remove + if ( cache[ id ] ) { + + delete cache[ id ]; + + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( deleteExpando ) { + delete elem[ internalKey ]; + + } else if ( typeof elem.removeAttribute !== core_strundefined ) { + elem.removeAttribute( internalKey ); + + } else { + elem[ internalKey ] = null; + } + + core_deletedIds.push( id ); + } + } + } + } + }, + + _evalUrl: function( url ) { + return jQuery.ajax({ + url: url, + type: "GET", + dataType: "script", + async: false, + global: false, + "throws": true + }); + } +}); +jQuery.fn.extend({ + wrapAll: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapAll( html.call(this, i) ); + }); + } + + if ( this[0] ) { + // The elements to wrap the target around + var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); + + if ( this[0].parentNode ) { + wrap.insertBefore( this[0] ); + } + + wrap.map(function() { + var elem = this; + + while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { + elem = elem.firstChild; + } + + return elem; + }).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapInner( html.call(this, i) ); + }); + } + + return this.each(function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + }); + }, + + wrap: function( html ) { + var isFunction = jQuery.isFunction( html ); + + return this.each(function(i) { + jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html ); + }); + }, + + unwrap: function() { + return this.parent().each(function() { + if ( !jQuery.nodeName( this, "body" ) ) { + jQuery( this ).replaceWith( this.childNodes ); + } + }).end(); + } +}); +var iframe, getStyles, curCSS, + ralpha = /alpha\([^)]*\)/i, + ropacity = /opacity\s*=\s*([^)]*)/, + rposition = /^(top|right|bottom|left)$/, + // swappable if display is none or starts with table except "table", "table-cell", or "table-caption" + // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rmargin = /^margin/, + rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ), + rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ), + rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" ), + elemdisplay = { BODY: "block" }, + + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: 0, + fontWeight: 400 + }, + + cssExpand = [ "Top", "Right", "Bottom", "Left" ], + cssPrefixes = [ "Webkit", "O", "Moz", "ms" ]; + +// return a css property mapped to a potentially vendor prefixed property +function vendorPropName( style, name ) { + + // shortcut for names that are not vendor prefixed + if ( name in style ) { + return name; + } + + // check for vendor prefixed names + var capName = name.charAt(0).toUpperCase() + name.slice(1), + origName = name, + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in style ) { + return name; + } + } + + return origName; +} + +function isHidden( elem, el ) { + // isHidden might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); +} + +function showHide( elements, show ) { + var display, elem, hidden, + values = [], + index = 0, + length = elements.length; + + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + values[ index ] = jQuery._data( elem, "olddisplay" ); + display = elem.style.display; + if ( show ) { + // Reset the inline display of this element to learn if it is + // being hidden by cascaded rules or not + if ( !values[ index ] && display === "none" ) { + elem.style.display = ""; + } + + // Set elements which have been overridden with display: none + // in a stylesheet to whatever the default browser style is + // for such an element + if ( elem.style.display === "" && isHidden( elem ) ) { + values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) ); + } + } else { + + if ( !values[ index ] ) { + hidden = isHidden( elem ); + + if ( display && display !== "none" || !hidden ) { + jQuery._data( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) ); + } + } + } + } + + // Set the display of most of the elements in a second loop + // to avoid the constant reflow + for ( index = 0; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + if ( !show || elem.style.display === "none" || elem.style.display === "" ) { + elem.style.display = show ? values[ index ] || "" : "none"; + } + } + + return elements; +} + +jQuery.fn.extend({ + css: function( name, value ) { + return jQuery.access( this, function( elem, name, value ) { + var len, styles, + map = {}, + i = 0; + + if ( jQuery.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + }, + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each(function() { + if ( isHidden( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + }); + } +}); + +jQuery.extend({ + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "columnCount": true, + "fillOpacity": true, + "fontWeight": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: { + // normalize float css property + "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" + }, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = jQuery.camelCase( name ), + style = elem.style; + + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // convert relative number strings (+= or -=) to relative numbers. #7345 + if ( type === "string" && (ret = rrelNum.exec( value )) ) { + value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) ); + // Fixes bug #9237 + type = "number"; + } + + // Make sure that NaN and null values aren't set. See: #7116 + if ( value == null || type === "number" && isNaN( value ) ) { + return; + } + + // If a number was passed in, add 'px' to the (except for certain CSS properties) + if ( type === "number" && !jQuery.cssNumber[ origName ] ) { + value += "px"; + } + + // Fixes #8908, it can be done more correctly by specifing setters in cssHooks, + // but it would mean to define eight (for every problematic property) identical functions + if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) { + + // Wrapped to prevent IE from throwing errors when 'invalid' values are provided + // Fixes bug #5509 + try { + style[ name ] = value; + } catch(e) {} + } + + } else { + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var num, val, hooks, + origName = jQuery.camelCase( name ); + + // Make sure that we're working with the right name + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + //convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Return, converting to number if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || jQuery.isNumeric( num ) ? num || 0 : val; + } + return val; + } +}); + +// NOTE: we've included the "window" in window.getComputedStyle +// because jsdom on node.js will break without it. +if ( window.getComputedStyle ) { + getStyles = function( elem ) { + return window.getComputedStyle( elem, null ); + }; + + curCSS = function( elem, name, _computed ) { + var width, minWidth, maxWidth, + computed = _computed || getStyles( elem ), + + // getPropertyValue is only needed for .css('filter') in IE9, see #12537 + ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined, + style = elem.style; + + if ( computed ) { + + if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right + // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels + // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values + if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret; + }; +} else if ( document.documentElement.currentStyle ) { + getStyles = function( elem ) { + return elem.currentStyle; + }; + + curCSS = function( elem, name, _computed ) { + var left, rs, rsLeft, + computed = _computed || getStyles( elem ), + ret = computed ? computed[ name ] : undefined, + style = elem.style; + + // Avoid setting ret to empty string here + // so we don't default to auto + if ( ret == null && style && style[ name ] ) { + ret = style[ name ]; + } + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + // but not position css attributes, as those are proportional to the parent element instead + // and we can't measure the parent instead because it might trigger a "stacking dolls" problem + if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) { + + // Remember the original values + left = style.left; + rs = elem.runtimeStyle; + rsLeft = rs && rs.left; + + // Put in the new values to get a computed value out + if ( rsLeft ) { + rs.left = elem.currentStyle.left; + } + style.left = name === "fontSize" ? "1em" : ret; + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + if ( rsLeft ) { + rs.left = rsLeft; + } + } + + return ret === "" ? "auto" : ret; + }; +} + +function setPositiveNumber( elem, value, subtract ) { + var matches = rnumsplit.exec( value ); + return matches ? + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) : + value; +} + +function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { + var i = extra === ( isBorderBox ? "border" : "content" ) ? + // If we already have the right measurement, avoid augmentation + 4 : + // Otherwise initialize for horizontal or vertical properties + name === "width" ? 1 : 0, + + val = 0; + + for ( ; i < 4; i += 2 ) { + // both box models exclude margin, so add it if we want it + if ( extra === "margin" ) { + val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); + } + + if ( isBorderBox ) { + // border-box includes padding, so remove it if we want content + if ( extra === "content" ) { + val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // at this point, extra isn't border nor margin, so remove border + if ( extra !== "margin" ) { + val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } else { + // at this point, extra isn't content, so add padding + val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // at this point, extra isn't content nor padding, so add border + if ( extra !== "padding" ) { + val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + return val; +} + +function getWidthOrHeight( elem, name, extra ) { + + // Start with offset property, which is equivalent to the border-box value + var valueIsBorderBox = true, + val = name === "width" ? elem.offsetWidth : elem.offsetHeight, + styles = getStyles( elem ), + isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // some non-html elements return undefined for offsetWidth, so check for null/undefined + // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 + // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 + if ( val <= 0 || val == null ) { + // Fall back to computed then uncomputed css if necessary + val = curCSS( elem, name, styles ); + if ( val < 0 || val == null ) { + val = elem.style[ name ]; + } + + // Computed unit is not pixels. Stop here and return. + if ( rnumnonpx.test(val) ) { + return val; + } + + // we need the check for style in case a browser which returns unreliable values + // for getComputedStyle silently falls back to the reliable elem.style + valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] ); + + // Normalize "", auto, and prepare for extra + val = parseFloat( val ) || 0; + } + + // use the active box-sizing model to add/subtract irrelevant styles + return ( val + + augmentWidthOrHeight( + elem, + name, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles + ) + ) + "px"; +} + +// Try to determine the default display value of an element +function css_defaultDisplay( nodeName ) { + var doc = document, + display = elemdisplay[ nodeName ]; + + if ( !display ) { + display = actualDisplay( nodeName, doc ); + + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + // Use the already-created iframe if possible + iframe = ( iframe || + jQuery("\n" + + ""); + } + + void html5NegatedOutput() { + // Negated test for overview-frame page + checkOutput("overview-frame.html", false, + "", + "\n" + + "
    \n" + + "
  • All Classes
  • ", + "
    \n" + + "

    Packages

    "); + + // Negated test for allclasses-frame page + checkOutput("allclasses-frame.html", false, + "", + "\n" + + "
      \n" + + "
    • "); + + // Negated test for allclasses-noframe page + checkOutput("allclasses-noframe.html", false, + "", + "\n" + + "
        \n" + + "
      • "); + + // Negated test for overview-summary page + checkOutput("overview-summary.html", false, + "", + "\n" + + "\n" + + "", + "
\n" + + "
", + "\n" + + "
\n" + + ""); + + // Negated test for package-frame page + checkOutput("pkg/package-frame.html", false, + "", + "\n" + + "

pkg

"); + + // Negated test for package-summary page + checkOutput("pkg/package-summary.html", false, + "", + "\n" + + "\n" + + "", + "", + "
", + "
", + "
", + "
", + "
"); + + // Negated test for package-tree page + checkOutput("pkg/package-tree.html", false, + "", + "\n" + + "\n" + + ""); + + // Negated test for package-use page + checkOutput("pkg1/package-use.html", false, + "", + "\n" + + "\n" + + "", + "
"); + + // Negated test for constant-values page + checkOutput("constant-values.html", false, + "", + "\n" + + "\n" + + "", + "\n" + + "\n" + + "
", + "
"); + + // Negated test for deprecated-list page + checkOutput("deprecated-list.html", false, + "", + "\n" + + "\n" + + "", + "\n" + + "\n" + + "
\n" + + "

Deprecated API

\n" + + "

Contents

", + "
", + "
", + "
", + "
", + "
", + "
", + "
", + "
", + "
", + "
"); + + // Negated test for serialized-form page + checkOutput("serialized-form.html", false, + "", + "\n" + + "\n" + + "", + "\n" + + "\n" + + "
", + "
  • \n" + + "

    Package pkg

    "); + + // Negated test for overview-tree page + checkOutput("overview-tree.html", false, + "", + "\n" + + "\n" + + "", + "\n" + + "
  • \n" + + "
    ", + "
    \n" + + "

    Class Hierarchy

    ", + "\n" + + "

    Interface Hierarchy

    ", + "\n" + + "

    Enum Hierarchy

    "); + + // Negated test for index-all page + checkOutput("index-all.html", false, + "", + "\n" + + "\n" + + "", + "\n" + + "
    \n" + + "
    "); + + // Negated test for src-html page + checkOutput("src-html/pkg/AnotherClass.html", false, + "", + "\n" + + "
    "); + + // Negated test for help-doc page + checkOutput("help-doc.html", false, + "", + "\n" + + "\n" + + "", + "\n" + + "
    \n" + + "
    ", + "
      \n" + + "
    • \n" + + "

      Overview

      ", + "
    • \n" + + "

      Package

      ", + "
    • \n" + + "

      Class/Interface

      "); + + // Negated test for a regular class page and members (nested class, field, constructore and method) + checkOutput("pkg/AnotherClass.html", false, + "", + "\n" + + "\n" + + "", + "\n" + + "
      ", + "\n" + + "
    ", + "\n" + + "
    ", + "\n" + + "
    ", + "\n" + + "
    ", + "\n" + + "
      \n" + + "
    • \n" + + "\n" + + "\n" + + "

      Field Detail

      ", + "\n" + + "
        \n" + + "
      • \n" + + "\n" + + "\n" + + "

        Constructor Detail

        ", + "\n" + + "
          \n" + + "
        • \n" + + "\n" + + "\n" + + "

          Method Detail

          "); + + // Negated test for enum page + checkOutput("pkg/AnotherClass.ModalExclusionType.html", false, + "", + "\n" + + "\n" + + "", + "\n" + + "
          ", + "\n" + + "
    ", + "\n" + + "
    ", + "\n" + + "
      \n" + + "
    • \n" + + "\n" + + "\n" + + "

      Enum Constant Detail

      ", + "\n" + + "
        \n" + + "
      • \n" + + "\n" + + "\n" + + "

        Method Detail

        "); + + // Negated test for interface page + checkOutput("pkg2/Interface.html", false, + "", + "\n" + + "\n" + + "", + "\n" + + "
        ", + "\n" + + "
    ", + "\n" + + "
      \n" + + "
    • \n" + + "\n" + + "\n" + + "

      Method Detail

      "); + + // Negated test for error page + checkOutput("pkg/TestError.html", false, + "", + "\n" + + "\n" + + "", + "\n" + + "
      ", + "\n" + + "
        \n" + + "
      • \n" + + "\n" + + "\n" + + "

        Constructor Summary

        ", + "\n" + + "
          \n" + + "
        • \n" + + "\n" + + "\n" + + "

          Constructor Detail

          "); + + // Negated test for exception page + checkOutput("pkg/TestException.html", false, + "", + "\n" + + "\n" + + "", + "\n" + + "
          ", + "\n" + + "
            \n" + + "
          • \n" + + "\n" + + "\n" + + "

            Constructor Summary

            ", + "\n" + + "
              \n" + + "
            • \n" + + "\n" + + "\n" + + "

              Constructor Detail

              "); + + // Negated test for annotation page + checkOutput("pkg2/TestAnnotationType.html", false, + "", + "\n" + + "\n" + + "", + "\n" + + "
              ", + "\n" + + "
    ", + "\n" + + "
    ", + "\n" + + "
      \n" + + "
    • \n" + + "\n" + + "\n" + + "

      Element Detail

      "); + + // Negated test for class use page + checkOutput("pkg1/class-use/RegClass.html", false, + "", + "\n" + + "\n" + + "", + "\n" + + "\n" + + "
      ", + "
    ", + "
  • \n" + + "\n" + + "\n" + + "

    Uses of RegClass in pkg

    \n" + + "
  • "); + + // Negated test for main index page + checkOutput("index.html", false, + "", + "\n" + + "
    \n"); + } + + void html4Output() { + // Test for overview-frame page + checkOutput("overview-frame.html", true, + "", + "\n" + + "
      \n" + + "
    • All Classes
    • ", + "
      \n" + + "

      Packages

      "); + + // Test for allclasses-frame page + checkOutput("allclasses-frame.html", true, + "", + "\n" + + "
        \n" + + "
      • "); + + // Test for allclasses-noframe page + checkOutput("allclasses-noframe.html", true, + "", + "\n" + + "
          \n" + + "
        • "); + + // Test for overview-summary page + checkOutput("overview-summary.html", true, + "", + "\n" + + "\n" + + "", + "
    \n" + + "
    ", + "\n" + + "
    \n" + + ""); + + // Test for package-frame page + checkOutput("pkg/package-frame.html", true, + "", + "\n" + + "

    pkg

    "); + + // Test for package-summary page + checkOutput("pkg/package-summary.html", true, + "", + "\n" + + "\n" + + "", + "", + "
    ", + "
    ", + "
    ", + "
    ", + "
    "); + + // Test for package-tree page + checkOutput("pkg/package-tree.html", true, + "", + "\n" + + "\n" + + "", + "
  • "); + + // Test for package-use page + checkOutput("pkg1/package-use.html", true, + "", + "\n" + + "\n" + + "", + "
  • "); + + // Test for constant-values page + checkOutput("constant-values.html", true, + "", + "\n" + + "\n" + + "", + "\n" + + "\n" + + "
    ", + "
    "); + + // Test for deprecated-list page + checkOutput("deprecated-list.html", true, + "", + "\n" + + "\n" + + "", + "\n" + + "\n" + + "
    \n" + + "

    Deprecated API

    \n" + + "

    Contents

    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    ", + "
    "); + + // Test for serialized-form page + checkOutput("serialized-form.html", true, + "", + "\n" + + "\n" + + "", + "\n" + + "\n" + + "
    ", + "
  • \n" + + "

    Package pkg

    "); + + // Test for overview-tree page + checkOutput("overview-tree.html", true, + "", + "\n" + + "\n" + + "", + "
  • ", + "\n" + + "
  • \n" + + "
    ", + "

    Hierarchy For All Packages

    \n" + + "Package Hierarchies:", + "
    \n" + + "

    Class Hierarchy

    ", + "\n" + + "

    Interface Hierarchy

    ", + "\n" + + "

    Enum Hierarchy

    "); + + // Test for index-all page + checkOutput("index-all.html", true, + "", + "\n" + + "\n" + + "", + "\n" + + "
    \n" + + "
    "); + + // Test for src-html page + checkOutput("src-html/pkg/AnotherClass.html", true, + "", + "\n" + + "
    "); + + // Test for help-doc page + checkOutput("help-doc.html", true, + "", + "\n" + + "\n" + + "", + "\n" + + "
    \n" + + "
    ", + "
      \n" + + "
    • \n" + + "

      Overview

      ", + "
    • \n" + + "

      Package

      ", + "
    • \n" + + "

      Class/Interface

      "); + + // Test for a regular class page and members (nested class, field, constructore and method) + checkOutput("pkg/AnotherClass.html", true, + "", + "\n" + + "\n" + + "", + "\n" + + "
      ", + "\n" + + "
    ", + "\n" + + "
    ", + "\n" + + "
    ", + "\n" + + "
    ", + "\n" + + "
      \n" + + "
    • \n" + + "\n" + + "\n" + + "

      Field Detail

      ", + "\n" + + "
        \n" + + "
      • \n" + + "\n" + + "\n" + + "

        Constructor Detail

        ", + "\n" + + "
          \n" + + "
        • \n" + + "\n" + + "\n" + + "

          Method Detail

          "); + + // Test for enum page + checkOutput("pkg/AnotherClass.ModalExclusionType.html", true, + "", + "\n" + + "\n" + + "", + "\n" + + "
          ", + "\n" + + "
    ", + "\n" + + "
    ", + "\n" + + "
      \n" + + "
    • \n" + + "\n" + + "\n" + + "

      Enum Constant Detail

      ", + "\n" + + "
        \n" + + "
      • \n" + + "\n" + + "\n" + + "

        Method Detail

        "); + + // Test for interface page + checkOutput("pkg2/Interface.html", true, + "", + "\n" + + "\n" + + "", + "\n" + + "
        ", + "\n" + + "
    ", + "\n" + + "
      \n" + + "
    • \n" + + "\n" + + "\n" + + "

      Method Detail

      "); + + // Test for error page + checkOutput("pkg/TestError.html", true, + "", + "\n" + + "\n" + + "", + "\n" + + "
      ", + "\n" + + "
        \n" + + "
      • \n" + + "\n" + + "\n" + + "

        Constructor Summary

        ", + "\n" + + "
          \n" + + "
        • \n" + + "\n" + + "\n" + + "

          Constructor Detail

          "); + + // Test for exception page + checkOutput("pkg/TestException.html", true, + "", + "\n" + + "\n" + + "", + "\n" + + "
          ", + "\n" + + "
            \n" + + "
          • \n" + + "\n" + + "\n" + + "

            Constructor Summary

            ", + "\n" + + "
              \n" + + "
            • \n" + + "\n" + + "\n" + + "

              Constructor Detail

              "); + + // Test for annotation page + checkOutput("pkg2/TestAnnotationType.html", true, + "", + "\n" + + "\n" + + "", + "\n" + + "
              ", + "\n" + + "
    ", + "\n" + + "
    ", + "\n" + + "
      \n" + + "
    • \n" + + "\n" + + "\n" + + "

      Element Detail

      "); + + // Test for class use page + checkOutput("pkg1/class-use/RegClass.html", true, + "", + "\n" + + "\n" + + "", + "\n" + + "\n" + + "
      ", + "
    ", + "
  • \n" + + "\n" + + "\n" + + "

    Uses of RegClass in pkg

    \n" + + "
  • "); + + // Test for main index page + checkOutput("index.html", true, + "", + "", + "\n" + + "
    \n" + + "
    \n" + + "
    \n" + + "\n" + + "
    "); + } + + void html4NegatedOutput() { + // Negated test for overview-frame page + checkOutput("overview-frame.html", false, + "", + "\n" + + "
      \n" + + "
    • All Classes
    • ", + "
      \n" + + "

      Packages

      "); + + // Negated test for allclasses-frame page + checkOutput("allclasses-frame.html", false, + "", + "\n" + + "
        \n" + + "
      • "); + + // Negated test for allclasses-noframe page + checkOutput("allclasses-noframe.html", false, + "", + "\n" + + "
          \n" + + "
        • "); + + // Negated test for overview-summary page + checkOutput("overview-summary.html", false, + "", + "\n" + + "\n" + + "", + "
    \n" + + "
    ", + "
    \n" + + "